diff --git a/orm/orm_client/ormcli/cmscli.py b/orm/orm_client/ormcli/cmscli.py index 87ee1a98..a858edaf 100644 --- a/orm/orm_client/ormcli/cmscli.py +++ b/orm/orm_client/ormcli/cmscli.py @@ -270,7 +270,7 @@ def add_to_parser(service_sub): parser_list_customer.add_argument('--metadata', action='append', nargs="+", type=str, help='') - # group + # create group parser_create_group = subparsers.add_parser('create_group', help='[<"X-RANGER-Client" ' 'header>] ') + # delete group parser_delete_group = subparsers.add_parser('delete_group', help='[<"X-RANGER-Client" ' 'header>] ') @@ -367,7 +368,7 @@ def add_to_parser(service_sub): parser_unassign_group_role.add_argument( '--domain', type=str, help='domain name') - # groups - add default users + # groups - add group default users parser_add_group_default_users = subparsers.add_parser( 'add_group_default_users', help='[<"X-RANGER-Client" ' @@ -381,7 +382,7 @@ def add_to_parser(service_sub): 'datafile', type=argparse.FileType('r'), help='') - # groups - delete default user + # groups - delete group default user parser_delete_group_default_user = \ subparsers.add_parser('delete_group_default_user', help='[<"X-RANGER-Client" header>] ') + parser_add_group_region_user.add_argument( + 'client', **cli_common.ORM_CLIENT_KWARGS) + parser_add_group_region_user.add_argument( + 'groupid', type=str, help='') + parser_add_group_region_user.add_argument( + 'regionid', type=str, help='') + parser_add_group_region_user.add_argument( + 'datafile', type=argparse.FileType('r'), + help='') + + # groups - delete group region user + parser_delete_group_region_user = subparsers.add_parser( + 'delete_group_region_user', + help='[<"X-RANGER-Client" header>] ' + ' ') + parser_delete_group_region_user.add_argument( + 'client', **cli_common.ORM_CLIENT_KWARGS) + parser_delete_group_region_user.add_argument('groupid', type=str, + help='') + parser_delete_group_region_user.add_argument('regionid', type=str, + help='') + parser_delete_group_region_user.add_argument('userid', type=str, + help='') + parser_delete_group_region_user.add_argument('userdomain', type=str, + help='') return parser @@ -457,6 +491,8 @@ def cmd_details(args): for meta in args.metadata: param += '%smetadata=%s' % (preparm(param), meta[0]) return requests.get, 'customers/%s' % param + + # following are groups CLIs elif args.subcmd == 'create_group': return requests.post, 'groups/' elif args.subcmd == 'delete_group': @@ -501,9 +537,19 @@ def cmd_details(args): elif args.subcmd == 'add_group_default_users': return requests.post, 'groups/%s/users' % args.groupid elif args.subcmd == 'delete_group_default_user': - return requests.delete, 'groups/%s/users/%s' % ( + return requests.delete, 'groups/%s/users/%s/%s' % ( args.groupid, - args.userid) + args.userid, + args.userdomain) + elif args.subcmd == 'add_group_region_users': + return requests.post, 'groups/%s/regions/%s/users' % ( + args.groupid, args.regionid) + elif args.subcmd == 'delete_group_region_user': + return requests.delete, 'groups/%s/regions/%s/users/%s/%s' % ( + args.groupid, + args.regionid, + args.userid, + args.userdomain) def get_token(timeout, args, host): diff --git a/orm/services/customer_manager/cms_rest/controllers/v1/orm/group/region_users.py b/orm/services/customer_manager/cms_rest/controllers/v1/orm/group/region_users.py new file mode 100644 index 00000000..ed3e3d6d --- /dev/null +++ b/orm/services/customer_manager/cms_rest/controllers/v1/orm/group/region_users.py @@ -0,0 +1,116 @@ +from oslo_db.exception import DBDuplicateEntry +from pecan import request, rest +from wsmeext.pecan import wsexpose + +from orm.common.orm_common.utils import api_error_utils as err_utils +from orm.common.orm_common.utils import utils +from orm.services.customer_manager.cms_rest.logger import get_logger +from orm.services.customer_manager.cms_rest.logic.error_base import ErrorStatus, NotFound +from orm.services.customer_manager.cms_rest.logic.group_logic import GroupLogic +from orm.services.customer_manager.cms_rest.model.GroupModels import \ + RegionUser, RegionUserResultWrapper +from orm.services.customer_manager.cms_rest.utils import authentication + +LOG = get_logger(__name__) + + +class RegionUserController(rest.RestController): + + @wsexpose([str], str, str, str, rest_content_types='json') + def get(self, group_id, region_id, user_id): + return ["This is groups region user controller for group id: " + group_id] + + @wsexpose([RegionUserResultWrapper], str, str, body=[RegionUser], + rest_content_types='json', status_code=200) + def post(self, group_id, region_id, users): + LOG.info("RegionUserController - Add users to group id {0} " + "region_id : {1}".format(group_id, region_id)) + authentication.authorize(request, 'groups:add_group_region_users') + try: + group_logic = GroupLogic() + result = group_logic.add_group_region_users(group_id, + region_id, + users, + request.transaction_id) + + LOG.info("RegionUserController - Users added: " + str(result)) + + event_details = 'Group {} - users assigned.'.format(group_id) + utils.audit_trail('added group users', + request.transaction_id, + request.headers, + group_id, + event_details=event_details) + + except DBDuplicateEntry as exception: + LOG.log_exception( + "DBDuplicateEntry - Group users already assigned.", exception) + print exception.message + raise err_utils.get_error( + request.transaction_id, + status_code=409, + message='Duplicate Entry - Group users already assigned.', + error_details=exception.message) + + except ErrorStatus as exception: + LOG.log_exception( + "ErrorStatus - Failed to add users", exception) + raise err_utils.get_error(request.transaction_id, + message=exception.message, + status_code=exception.status_code) + except Exception as exception: + LOG.log_exception( + "Exception - Failed in add region users", exception) + raise err_utils.get_error(request.transaction_id, + status_code=500, + error_details=str(exception)) + + return result + + @wsexpose(None, str, str, str, str, status_code=204) + def delete(self, group_id, region_id, user, user_domain): + requester = request.headers.get('X-RANGER-Requester') + is_rds_client_request = requester == 'rds_resource_service_proxy' + LOG.info("Remove users from group id: {0} user: {1} ".format( + group_id, user)) + + authentication.authorize(request, 'groups:delete_group_region_user') + try: + group_logic = GroupLogic() + group_logic.delete_group_region_user(group_id, + region_id, + user, + user_domain, + request.transaction_id) + + LOG.info("UserController - Remove user from group finished") + + event_details = 'Group {} users unassigned'.format(group_id) + utils.audit_trail('delete group user', + request.transaction_id, + request.headers, + group_id, + event_details=event_details) + + except ValueError as exception: + raise err_utils.get_error(request.transaction_id, + message=exception.message, + status_code=404) + except ErrorStatus as exception: + LOG.log_exception("ErrorStatus - Failed to delete user from group", + exception) + raise err_utils.get_error(request.transaction_id, + message=exception.message, + status_code=exception.status_code) + + except NotFound as e: + raise err_utils.get_error(request.transaction_id, + message=e.message, + status_code=404) + + except Exception as exception: + LOG.log_exception("Exception - Failed in delete default user", + exception) + raise err_utils.get_error(request.transaction_id, + status_code=500, + error_details=str(exception)) diff --git a/orm/services/customer_manager/cms_rest/controllers/v1/orm/group/regions.py b/orm/services/customer_manager/cms_rest/controllers/v1/orm/group/regions.py index 2249112b..39e95f22 100644 --- a/orm/services/customer_manager/cms_rest/controllers/v1/orm/group/regions.py +++ b/orm/services/customer_manager/cms_rest/controllers/v1/orm/group/regions.py @@ -4,6 +4,8 @@ from wsmeext.pecan import wsexpose from orm.common.orm_common.utils import api_error_utils as err_utils from orm.common.orm_common.utils import utils +from orm.services.customer_manager.cms_rest.controllers.v1.orm.group.region_users \ + import RegionUserController from orm.services.customer_manager.cms_rest.logger import get_logger from orm.services.customer_manager.cms_rest.logic.error_base import ErrorStatus from orm.services.customer_manager.cms_rest.logic.group_logic import GroupLogic @@ -16,6 +18,8 @@ LOG = get_logger(__name__) class RegionController(rest.RestController): + users = RegionUserController() + @wsexpose([str], str, str, rest_content_types='json') def get(self, group_id, region_id): return ["This is groups region controller ", "group id: " + group_id] diff --git a/orm/services/customer_manager/cms_rest/controllers/v1/orm/group/users.py b/orm/services/customer_manager/cms_rest/controllers/v1/orm/group/users.py index 7c62ab4d..e18bb41c 100644 --- a/orm/services/customer_manager/cms_rest/controllers/v1/orm/group/users.py +++ b/orm/services/customer_manager/cms_rest/controllers/v1/orm/group/users.py @@ -5,7 +5,7 @@ from wsmeext.pecan import wsexpose from orm.common.orm_common.utils import api_error_utils as err_utils from orm.common.orm_common.utils import utils from orm.services.customer_manager.cms_rest.logger import get_logger -from orm.services.customer_manager.cms_rest.logic.error_base import ErrorStatus +from orm.services.customer_manager.cms_rest.logic.error_base import ErrorStatus, NotFound from orm.services.customer_manager.cms_rest.logic.group_logic import GroupLogic from orm.services.customer_manager.cms_rest.model.GroupModels import \ User, UserResultWrapper @@ -23,14 +23,15 @@ class UserController(rest.RestController): @wsexpose(UserResultWrapper, str, body=[User], rest_content_types='json', status_code=200) def post(self, group_id, users): - LOG.info("UserController - Add users to group id {0} " - "users: {1}".format(group_id, users)) - authentication.authorize(request, 'groups:add_default_user') + LOG.info("UserController - Add Default users to group id {0} " + "users: {1}".format(group_id, str(users))) + authentication.authorize(request, 'groups:add_group_default_users') try: group_logic = GroupLogic() - result = group_logic.add_default_users(group_id, - users, - request.transaction_id) + result = \ + group_logic.add_group_default_users(group_id, + users, + request.transaction_id) LOG.info("UserController - Users added: " + str(result)) @@ -66,20 +67,19 @@ class UserController(rest.RestController): return result - @wsexpose(None, str, str, status_code=204) - def delete(self, group_id, user): + @wsexpose(None, str, str, str, status_code=204) + def delete(self, group_id, user, user_domain): requester = request.headers.get('X-RANGER-Requester') is_rds_client_request = requester == 'rds_resource_service_proxy' - LOG.info("Remove users from group id: {0} user: {1} ".format( - group_id, user)) - authentication.authorize(request, 'groups:delete_default_user') + authentication.authorize(request, 'groups:delete_group_default_user') try: group_logic = GroupLogic() - group_logic.delete_default_user(group_id, - user, - request.transaction_id) + group_logic.delete_group_default_user(group_id, + user, + user_domain, + request.transaction_id) LOG.info("UserController - Remove user from group finished") @@ -100,6 +100,12 @@ class UserController(rest.RestController): raise err_utils.get_error(request.transaction_id, message=exception.message, status_code=exception.status_code) + + except NotFound as e: + raise err_utils.get_error(request.transaction_id, + message=e.message, + status_code=404) + except Exception as exception: LOG.log_exception("Exception - Failed in delete default user", exception) diff --git a/orm/services/customer_manager/cms_rest/data/data_manager.py b/orm/services/customer_manager/cms_rest/data/data_manager.py index b63b5c52..c9754616 100755 --- a/orm/services/customer_manager/cms_rest/data/data_manager.py +++ b/orm/services/customer_manager/cms_rest/data/data_manager.py @@ -196,11 +196,11 @@ class DataManager(object): def add_user(self, user): db_user = self.session.query(CmsUser).filter( - CmsUser.name == user.id).first() + CmsUser.name == user).first() if not (db_user is None): return db_user - db_user = CmsUser(name=user.id) + db_user = CmsUser(name=user) self.session.add(db_user) self.flush() diff --git a/orm/services/customer_manager/cms_rest/data/sql_alchemy/groups_user_record.py b/orm/services/customer_manager/cms_rest/data/sql_alchemy/groups_user_record.py index 754ad971..58020209 100644 --- a/orm/services/customer_manager/cms_rest/data/sql_alchemy/groups_user_record.py +++ b/orm/services/customer_manager/cms_rest/data/sql_alchemy/groups_user_record.py @@ -5,6 +5,7 @@ from orm.services.customer_manager.cms_rest.data.sql_alchemy.models \ from orm.services.customer_manager.cms_rest.data.sql_alchemy.region_record \ import RegionRecord from orm.services.customer_manager.cms_rest.logger import get_logger +from orm.services.customer_manager.cms_rest.logic.error_base import NotFound LOG = get_logger(__name__) @@ -94,27 +95,65 @@ class GroupsUserRecord: def remove_user_from_group(self, group_uuid, region_id, - domain_name, - user_name): + domain, + user_id): - user_record = CmsUserRecord(self.session) - user_id = user_record.get_cms_user_id_from_name(user_name) + # Check if 'region_id' is a string - if so, get corresponding + # cms_region id value for use later to query/delete the + # corresponding group user record + if isinstance(region_id, basestring): + region_query = region_id + region_record = RegionRecord(self.session) + region_id = region_record.get_region_id_from_name(region_id) + if region_id is None: + raise NotFound("region {} ".format(region_query)) - cmd = 'DELETE FROM groups_user WHERE group_id = %s and \ - region_id = %s and domain_name = %s and user_id = %s' - result = self.session.connection().execute(cmd, - (group_uuid, - region_id, - domain_name, - user_id)) - self.session.flush() + # get cms_user id value for user_id (contains user name) + # to query/delete the corresponding group user record + user_name = user_id + cms_user_record = CmsUserRecord(self.session) + user_id = cms_user_record.get_cms_user_id_from_name(user_id) + if user_id is None: + raise NotFound("user {} ".format(user_name)) - if result.rowcount == 0: - LOG.warn('user with user name {0} not found'.format( - user_name)) - raise ValueError( - 'user with user name {0} not found'.format( - user_name)) + # when deleting user from a specific region, verify that user + # is associated with the group and region in the delete request + if region_id > -1: + user_check = 'SELECT DISTINCT user_id from groups_user \ + WHERE group_id =%s AND region_id =%s \ + AND user_id =%s AND domain_name =%s' - LOG.debug("num records deleted: " + str(result.rowcount)) + result = self.session.connection().execute(user_check, + group_uuid, + region_id, + user_id, domain) + if result.rowcount == 0: + raise NotFound("user {}@{} domain".format(user_name, domain)) + + if region_id == -1: + cmd = "DELETE ur FROM groups_user ur,groups_user u \ + WHERE ur.user_id=u.user_id AND ur.domain_name=%s \ + AND ur.group_id = u.group_id AND u.region_id =-1 \ + AND ur.group_id = %s AND ur.user_id= %s" + result = self.session.connection().execute(cmd, + domain, + group_uuid, user_id) + + else: + # DELETE command to identify whether or not the provided region + # user/user_domain combo is also a default user/user_domain for + # the group; if it is, NO group_user record(s) will be deleted + del_cmd = "DELETE ur FROM groups_user as ur \ + LEFT JOIN groups_user AS u \ + ON ur.group_id = u.group_id AND u.user_id=ur.user_id \ + AND u.region_id =-1 AND ur.domain_name = u.domain_name \ + WHERE ur.group_id = %s AND ur.region_id= %s \ + AND ur.user_id= %s AND ur.domain_name = %s \ + AND u.user_id IS NULL" + + result = self.session.connection().execute(del_cmd, + group_uuid, + region_id, + user_id, + domain) return result diff --git a/orm/services/customer_manager/cms_rest/data/sql_alchemy/models.py b/orm/services/customer_manager/cms_rest/data/sql_alchemy/models.py index d1655fbb..055817ad 100755 --- a/orm/services/customer_manager/cms_rest/data/sql_alchemy/models.py +++ b/orm/services/customer_manager/cms_rest/data/sql_alchemy/models.py @@ -101,6 +101,12 @@ class Groups(Base, CMSBaseModel): return proxy_dict + def get_default_region(self): + for region in self.group_regions: + if region.region_id == -1: + return region + return None + def get_group_regions(self): group_regions = [] for group_region in self.group_regions: @@ -108,6 +114,12 @@ class Groups(Base, CMSBaseModel): group_regions.append(group_region) return group_regions + def get_region(self, region_id): + for region in self.group_regions: + if region.region_id == region_id: + return region + return None + def to_wsme(self): uuid = self.uuid name = self.name @@ -116,6 +128,9 @@ class Groups(Base, CMSBaseModel): enabled = True if self.enabled else False regions = [group_region.to_wsme() for group_region in self.group_regions if group_region.region_id != -1] + # users = [groups_user.to_wsme() for groups_user in self.groups_users if + # groups_user.region_id == -1] + result = GroupWsmeModels.Group(description=description, name=name, uuid=uuid, @@ -136,12 +151,22 @@ class GroupsRegion(Base, CMSBaseModel): group_id = Column(String(64), ForeignKey('groups.uuid'), primary_key=True, nullable=False, index=True) region_id = Column(Integer, ForeignKey('cms_region.id'), primary_key=True, nullable=False, index=True) + group_region_users = relationship("GroupsUser", + uselist=True, + order_by="GroupsUser.user_id", + primaryjoin="and_(GroupsRegion.group_id==GroupsUser.group_id," + "GroupsRegion.region_id==GroupsUser.region_id)") + region = relationship("Region", viewonly=True) + groups_users = relationship( + "GroupsUser", cascade="all, delete, delete-orphan") + def __json__(self): return dict( group_id=self.group_id, - region_id=self.region_id + region_id=self.region_id, + group_region_users=[groups_user.__json__() for groups_user in self.group_region_users] ) def get_proxy_dict(self): @@ -155,8 +180,25 @@ class GroupsRegion(Base, CMSBaseModel): def to_wsme(self): name = self.region.name type = self.region.type + # users = [groups_user.to_wsme() for groups_user in self.group_region_users if + # groups_user.region_id > -1] + + users = [] + user = None + for user_role in self.group_region_users: + if user and user.id != user_role.user.name: + users.append(user) + user = GroupWsmeModels.User(id=[user_role.user.name], domain=user_role.domain_name) + elif user is None: + user = GroupWsmeModels.User(id=[user_role.user.name], domain=user_role.domain_name) + else: + user.role.append(user_role.user.name) + if user: + users.append(user) + region = GroupWsmeModels.Region(name=name, - type=type) + type=type, + users=users) return region @@ -207,7 +249,8 @@ class GroupsCustomerRole(Base, CMSBaseModel): group_id = Column(String(64), ForeignKey('groups.uuid'), primary_key=True, nullable=False) - region_id = Column(Integer, ForeignKey('cms_region.id')) + region_id = Column(Integer, ForeignKey('cms_region.id'), + primary_key=True) customer_id = Column(Integer, ForeignKey('customer.id'), primary_key=True, nullable=False, index=True) @@ -256,7 +299,8 @@ class GroupsDomainRole(Base, CMSBaseModel): group_id = Column(String(64), ForeignKey('groups.uuid'), primary_key=True, nullable=False) - region_id = Column(Integer, ForeignKey('cms_region.id')) + region_id = Column(Integer, ForeignKey('cms_region.id'), + primary_key=True) domain_name = Column(String(64), ForeignKey('cms_domain.name'), primary_key=True, nullable=False) @@ -286,7 +330,7 @@ class GroupsDomainRole(Base, CMSBaseModel): ''' -' GroupRole is a DataObject and contains all the fields defined in GroupRole +' GroupsUser is a DataObject and contains all the fields defined in GroupRole ' table record, defined as SqlAlchemy model map to a table ''' @@ -304,7 +348,7 @@ class GroupsUser(Base, CMSBaseModel): primary_key=True, nullable=False, index=True) domain_name = Column(String(64), ForeignKey('cms_domain.name'), - nullable=False) + primary_key=True, nullable=False) user = relationship("CmsUser", viewonly=True) groups = relationship("Groups", viewonly=True) @@ -329,6 +373,12 @@ class GroupsUser(Base, CMSBaseModel): "domain_name": self.domain_name } + def to_wsme(self): + id = [] + domain = "" + + user = GroupWsmeModels.User(id=id, domain=domain) + return user ''' ' CmsRole is a DataObject and contains all the fields defined in CmsRole diff --git a/orm/services/customer_manager/cms_rest/etc/policy.json b/orm/services/customer_manager/cms_rest/etc/policy.json index 568c4f8e..c345184c 100755 --- a/orm/services/customer_manager/cms_rest/etc/policy.json +++ b/orm/services/customer_manager/cms_rest/etc/policy.json @@ -46,6 +46,9 @@ "groups:delete_region": "rule:admin_or_creator", "groups:assign_role": "rule:admin_or_support_or_creator", "groups:unassign_role": "rule:admin_or_creator", - "groups:add_default_user": "rule:admin_or_support", - "groups:delete_default_user": "rule:admin" + "groups:add_group_default_users": "rule:admin_or_support", + "groups:delete_group_default_user": "rule:admin", + "groups:add_group_region_users": "rule:admin_or_support", + "groups:delete_group_region_user": "rule:admin" + } diff --git a/orm/services/customer_manager/cms_rest/logic/customer_logic.py b/orm/services/customer_manager/cms_rest/logic/customer_logic.py index 43ccfb98..063dbf88 100755 --- a/orm/services/customer_manager/cms_rest/logic/customer_logic.py +++ b/orm/services/customer_manager/cms_rest/logic/customer_logic.py @@ -82,11 +82,10 @@ class CustomerLogic(object): for sql_user in existing_default_users_roles: default_users_dic[sql_user.name] = sql_user - for user in default_users_requested: is_default_user_exist = user.id in default_users_dic.keys() if not is_default_user_exist: - sql_user = datamanager.add_user(user) + sql_user = datamanager.add_user(user.id) default_region_users.append(sql_user) sql_user.sql_roles = [] for role in user.role: @@ -127,7 +126,7 @@ class CustomerLogic(object): for user in users: is_default_user_in_region = user.id in default_users_dic.keys() if not is_default_user_in_region: - sql_user = datamanager.add_user(user) + sql_user = datamanager.add_user(user.id) for role in user.role: sql_role = datamanager.add_role(role) users_roles.append((sql_user, sql_role)) diff --git a/orm/services/customer_manager/cms_rest/logic/group_logic.py b/orm/services/customer_manager/cms_rest/logic/group_logic.py index 91fa4e5c..e8af790a 100755 --- a/orm/services/customer_manager/cms_rest/logic/group_logic.py +++ b/orm/services/customer_manager/cms_rest/logic/group_logic.py @@ -17,7 +17,8 @@ from orm.services.customer_manager.cms_rest.model.GroupModels import ( GroupSummaryResponse, RegionResultWrapper, RoleResultWrapper, - UserResultWrapper) + UserResultWrapper, + RegionUserResultWrapper) from orm.services.customer_manager.cms_rest.rds_proxy import RdsProxy @@ -57,27 +58,33 @@ class GroupLogic(object): ' already associated with group') raise ex - def add_default_users(self, - group_uuid, - users, - transaction_id): - LOG.info("Add default users: group: {} user: {} ".format( - group_uuid, users)) + def add_default_user_db(self, datamanager, default_users_requested, existing_default_users, group_uuid): + default_region_users = [] - users_result = [{'id': user.id, - 'domain': user.domain} for user in users] - user_result_wrapper = build_response(group_uuid, - transaction_id, - 'add_default_users', - users=users_result) - return user_result_wrapper + for user_info in default_users_requested: + domain_value = user_info.domain + for username in user_info.id: + default_user_exists = [] + if existing_default_users: + # check if there is user/user_domain match + # in existing_default_users list - def delete_default_user(self, - group_uuid, - user, - transaction_id): - LOG.info("Delete default user: group: {} user: {} ".format( - group_uuid, user)) + # note: check with Hari or James on how to do + # an efficient search within a dictionary + default_user_exists =\ + [username for exist_user in existing_default_users + if exist_user.user.name == username and + exist_user.domain_name == domain_value] + + if not default_user_exists: + # add user to cms_user table and group_users + sql_user = datamanager.add_user(username) + sql_groups_user = \ + datamanager.add_groups_user(group_uuid, sql_user.id, + -1, domain_value) + default_region_users.append(sql_groups_user) + + return default_region_users def assign_roles(self, group_uuid, @@ -136,6 +143,255 @@ class GroupLogic(object): datamanager.rollback() raise + def add_group_default_users(self, group_uuid, users, transaction_id, + p_datamanager=None): + + LOG.info("Add default users: group: {} user: {} ".format( + group_uuid, users)) + datamanager = None + try: + # p_datamanager is passed by replace_default_users + if p_datamanager is None: + datamanager = DataManager() + datamanager.begin_transaction() + # else: + # datamanager = p_datamanager + + group_id = datamanager.get_group_by_uuid_or_name(group_uuid) + + if group_id is None: + raise ErrorStatus(404, "group {} does not exist".format( + group_uuid)) + + group_record = datamanager.get_record('group') + group = group_record.read_group_by_uuid(group_uuid) + + defaultRegion = group.get_default_region() + + # get all existing default region users with their respective user domain + existing_default_users = defaultRegion.group_region_users if defaultRegion else [] + + default_users = [] + for default_user in existing_default_users: + if default_user.user not in default_users: + default_users.append(default_user) + + default_region_users =\ + self.add_default_user_db(datamanager, users, default_users, + group_uuid) + # add default user(s) to all regions where group is assigned to + regions = group.get_group_regions() + for region in regions: + for user in default_region_users: + datamanager.add_groups_user(group_uuid, user.user_id, + region.region_id, user.domain_name) + + timestamp = utils.get_time_human() + datamanager.flush() # i want to get any exception created by this insert + + ''' + # if len(customer.customer_customer_regions) > 1: + # call rds logic + # if regions: + # RdsProxy.send_group_dict(group, transaction_id, "PUT") + ''' + + if p_datamanager is None: + users_result = [{'id': user.id, + 'domain': user.domain} for user in users] + user_result_wrapper = build_response(group_uuid, + transaction_id, + 'add_group_default_users', + users=users_result) + + datamanager.commit() + return user_result_wrapper + + except Exception as exception: + datamanager.rollback() + if 'Duplicate' in exception.message: + raise ErrorStatus(409, exception.message) + LOG.log_exception("Failed to add_group_default_users", exception) + raise + + # this function is used to assign users to a specific region + def add_group_region_users(self, group_uuid, region_id, + region_users_requested, + transaction_id, p_datamanager=None): + LOG.info("Add user under group region: group: {} " + "region: {}".format(group_uuid, region_id)) + datamanager = None + + try: + # p_datamanager is passed by replace_default_users + if p_datamanager is None: + datamanager = DataManager() + datamanager.begin_transaction() + # else: + # datamanager = p_datamanager + + group_id = datamanager.get_group_by_uuid_or_name(group_uuid) + region_id = datamanager.get_region_id_by_name(region_id) + + if group_id is None: + raise ErrorStatus(404, "group {} does not exist".format(group_uuid)) + + if region_id is None: + raise ErrorStatus(404, "region {} does not exist".format(region_uuid)) + + group_record = datamanager.get_record('group') + group = group_record.read_group_by_uuid(group_uuid) + groupRegion = group.get_region(region_id) + + # get all users already assigned to the group region + current_region_users = groupRegion.group_region_users if groupRegion else [] + + # build the existing_users_list from current region users result + existing_users_list = [] + for rgn_user in current_region_users: + if rgn_user.user not in existing_users_list: + existing_users_list.append(rgn_user) + + # This section determines when to add region user to database. + # Only requested users that are not in the existing user list shall be + # added to the database + for user_info in region_users_requested: + domain_value = user_info.domain + for username in user_info.id: + region_user_exists = [] + if existing_users_list: + # check if there is user/user_domain match + # in existing_default_users list + region_user_exists =\ + [username for exist_user in existing_users_list + if exist_user.user.name == username and + exist_user.domain_name == domain_value] + + if not region_user_exists: + # add user to cms_user table and group_users + sql_user = datamanager.add_user(username) + sql_groups_user = \ + datamanager.add_groups_user(group_uuid, sql_user.id, + region_id, domain_value) + + timestamp = utils.get_time_human() + datamanager.flush() # i want to get any exception created by this insert + + ''' + # if len(customer.customer_customer_regions) > 1: + # call rds logic + # if regions: + # RdsProxy.send_customer(customer, transaction_id, "PUT") + ''' + + if p_datamanager is None: + users_result = [{'id': user.id, + 'domain': user.domain} for user in region_users_requested] + region_user_result_wrapper = build_response(group_uuid, + transaction_id, + 'add_group_region_users', + users=users_result) + + datamanager.commit() + return region_user_result_wrapper + + except Exception as exception: + datamanager.rollback() + if 'Duplicate' in exception.message: + raise ErrorStatus(409, exception.message) + LOG.log_exception("Failed to add_group_region_users", exception) + raise + + def delete_group_default_user(self, group_uuid, user, domain, + transaction_id): + + LOG.info("Delete default user: group: {0} user: {1} " + " user domain: {2}".format(group_uuid, user, domain)) + + datamanager = DataManager() + + try: + group = datamanager.get_group_by_uuid_or_name(group_uuid) + if group is None: + raise ErrorStatus(404, "group {} does not exist".format( + group_uuid)) + + user_record = datamanager.get_record('groups_user') + result = user_record.remove_user_from_group(group_uuid, -1, + domain, user) + + if result.rowcount == 0: + raise NotFound("user {}@{} domain".format(user, domain)) + datamanager.flush() + + # if len(customer.customer_customer_regions) > 1: + # RdsProxy.send_customer(customer, transaction_id, "PUT") + + datamanager.commit() + + # following log info does not yet include user_domain + LOG.info("User {0} from region {1} in group {2} deleted". + format(user, 'DEFAULT', group_uuid)) + + except NotFound as e: + datamanager.rollback() + LOG.log_exception("Failed to delete default user, user not found", + e.message) + raise NotFound("Failed to delete default user, default %s not found" % + e.message) + raise + + except Exception as exp: + datamanager.rollback() + raise exp + + def delete_group_region_user(self, group_uuid, region_id, user, + user_domain, transaction_id): + LOG.info("Delete user: group: {0} region: {1} user: {2} user " + "domain: {3}".format(group_uuid, region_id, user, user_domain)) + + datamanager = DataManager() + + try: + group = datamanager.get_group_by_uuid_or_name(group_uuid) + if group is None: + raise ErrorStatus(404, "group {} does not exist".format( + group_uuid)) + user_record = datamanager.get_record('groups_user') + result = user_record.remove_user_from_group(group_uuid, region_id, + user_domain, user) + + if result.rowcount == 0: + '''result.rowcount = 0 indicates that the region user + requested for deletion is identified as default user for the + group. Since default user supersedes region user, use + 'delete_group_default_user' command instead to delete the user. + + ''' + message = "Cannot use 'delete_group_region_user' as user " \ + "%s@%s domain is a default user for "\ + "group %s. Use 'delete_group_default_user' "\ + "instead." % (user, user_domain, group_uuid) + raise ErrorStatus(400, message) + + # RdsProxy.send_customer(customer, transaction_id, "PUT") + datamanager.commit() + + LOG.info("User {0} with user domain {1} from region {2} " + "in group {3} deleted".format(user, user_domain, + region_id, group_uuid)) + + except NotFound as e: + datamanager.rollback() + LOG.log_exception("Failed to delete region user, user not found", + e.message) + raise NotFound("Failed to delete region user, region %s not found" % + e.message) + except Exception as exception: + datamanager.rollback() + LOG.log_exception("Failed to delete region user", exception) + raise exception + def unassign_roles(self, group_uuid, role_name, @@ -388,7 +644,6 @@ class GroupLogic(object): resp = requests.get(conf.api.rds_server.base + conf.api.rds_server.status + sql_group.uuid, verify=conf.verify).json() - for item in ret_group.regions: for status in resp['regions']: if status['region'] == item.name: @@ -520,12 +775,12 @@ class GroupLogic(object): raise -def build_response(group_uuid, transaction_id, context, roles=[], users=[]): +def build_response(group_uuid, transaction_id, context, users=[]): """this function generate th group action response JSON :param group_uuid: :param transaction_id: :param context: - :param roles: + :param users: :return: """ timestamp = utils.get_time_human() @@ -548,10 +803,16 @@ def build_response(group_uuid, transaction_id, context, roles=[], users=[]): links={'self': base_link}, created=timestamp) - elif context == 'add_default_users': + elif context == 'add_group_default_users': return UserResultWrapper(transaction_id=transaction_id, users=users, links={'self': base_link}, created=timestamp) + elif context == 'add_group_region_users': + return RegionUserResultWrapper(transaction_id=transaction_id, + users=users, + links={'self': base_link}, + created=timestamp) + else: return None diff --git a/orm/services/customer_manager/cms_rest/model/GroupModels.py b/orm/services/customer_manager/cms_rest/model/GroupModels.py index d63c2a08..a922db68 100755 --- a/orm/services/customer_manager/cms_rest/model/GroupModels.py +++ b/orm/services/customer_manager/cms_rest/model/GroupModels.py @@ -7,34 +7,6 @@ import wsme from wsme import types as wtypes -class Region(Model): - """network model the region - """ - name = wsme.wsattr(wsme.types.text, mandatory=True) - type = wsme.wsattr(wsme.types.text, default="single", mandatory=False) - status = wsme.wsattr(wsme.types.text, mandatory=False) - error_message = wsme.wsattr(wsme.types.text, mandatory=False) - - def __init__(self, name="", type="single", users=[], status="", - error_message=""): - """Create a new region. - - :param name: region name - :param type: region type - :param quotas: quotas ( array of Quota) - :param users: array of users of specific region - :param status: status of creation - :param error_message: error message if status is error - """ - - self.name = name - self.type = type - self.users = users - self.status = status - if error_message: - self.error_message = error_message - - class RoleAssignment(Model): roles = wsme.wsattr([str], mandatory=True) customer = wsme.wsattr(wsme.types.text, mandatory=False) @@ -57,10 +29,16 @@ class RoleAssignment(Model): class User(Model): - id = wsme.wsattr(wsme.types.text, mandatory=True) + # id = wsme.wsattr(wsme.types.text, mandatory=True) + id = wsme.wsattr([str]) domain = wsme.wsattr(wsme.types.text, mandatory=True) - def __init__(self, id="", domain=""): + def __init__(self, id=[], domain=""): + """Create a new user + + :param id: list of users + :param domain: user domain + """ self.id = id self.domain = domain @@ -76,41 +54,68 @@ class UserUsers(Model): # if len(set(self.users)) != len(self.users) and # len(set(self.domain)) != len(self.users): # raise ErrorStatus(400, "Duplicate regions found") + pass # Remove the below return once implementation is done return None -class UserRegions(Model): - name = wsme.wsattr(wsme.types.text, mandatory=False) - type = wsme.wsattr(wsme.types.text, default="single", mandatory=False) - users = wsme.wsattr([UserUsers], mandatory=False) +class RegionUser(Model): + id = wsme.wsattr([str]) + domain = wsme.wsattr(wsme.types.text, mandatory=True) - def __init__(self, name="", type="", users=[]): - self.name = name - self.type = type - self.users = users + def __init__(self, id=[], domain=""): + self.id = id + self.domain = domain -class UserAssignment(Model): - userUsers = wsme.wsattr([UserUsers], mandatory=True) - userRegions = wsme.wsattr([UserRegions], mandatory=True) - - def __init__(self, status="", userUsers=None, userRegions=""): - self.userUsers = userUsers - self.userRegions = userRegions - - def validate_model(self): - - if not userUsers and not userRegions: - raise ErrorStatus(400, "Either regions or users" - "is required. ") +# class UserAssignment(Model): +# userUsers = wsme.wsattr([UserUsers], mandatory=True) +# userRegions = wsme.wsattr([UserRegions], mandatory=True) +# +# def __init__(self, status="", userUsers=None, userRegions=""): +# self.userUsers = userUsers +# self.userRegions = userRegions +# +# def validate_model(self): +# +# if not userUsers and not userRegions: +# raise ErrorStatus(400, "Either regions or users" +# "is required. ") # check no duplicate users in dictonary list # for userRegion in userRegions: # if dups found issue 400 duplicate region error +class Region(Model): + """network model the region + """ + name = wsme.wsattr(wsme.types.text, mandatory=True) + type = wsme.wsattr(wsme.types.text, default="single", mandatory=False) + users = wsme.wsattr([User], mandatory=False) + status = wsme.wsattr(wsme.types.text, mandatory=False) + error_message = wsme.wsattr(wsme.types.text, mandatory=False) + + def __init__(self, name="", type="single", users=[], status="", + error_message=""): + """Create a new region. + + :param name: region name + :param type: region type + :param users: array of users of specific region + :param status: status of creation + :param error_message: error message if status is error + """ + + self.name = name + self.type = type + self.users = users + self.status = status + if error_message: + self.error_message = error_message + + class Group(Model): """group entity with all it's related data """ @@ -121,9 +126,11 @@ class Group(Model): uuid = wsme.wsattr(wsme.types.text, mandatory=False) enabled = wsme.wsattr(bool, mandatory=True) regions = wsme.wsattr([Region], mandatory=False) + users = wsme.wsattr([User], mandatory=False) def __init__(self, description="", name="", enabled=False, - regions=[], status="", domain='default', uuid=None): + regions=[], users=[], status="", domain='default', + uuid=None): """Create a new Group. :param description: Server name @@ -135,6 +142,7 @@ class Group(Model): self.domain = domain self.enabled = enabled self.regions = regions + self.users = users if uuid is not None: self.uuid = uuid @@ -299,10 +307,11 @@ class RoleResultWrapper(Model): class UserResult(Model): - id = wsme.wsattr(wsme.types.text, mandatory=True) + id = wsme.wsattr([str], mandatory=True) domain = wsme.wsattr(wsme.types.text, mandatory=True) - def __init__(self, id="", domain=""): + # def __init__(self, id="", domain=""): + def __init__(self, id=[], domain=""): Model.__init__(self) self.id = id self.domain = domain @@ -321,3 +330,28 @@ class UserResultWrapper(Model): self.transaction_id = transaction_id self.links = links self.created = created + + +class RegionUserResult(Model): + id = wsme.wsattr([str], mandatory=True) + domain = wsme.wsattr(wsme.types.text, mandatory=True) + + def __init__(self, id=[], domain=""): + Model.__init__(self) + self.id = id + self.domain = domain + + +class RegionUserResultWrapper(Model): + transaction_id = wsme.wsattr(wsme.types.text, mandatory=True) + users = wsme.wsattr([RegionUserResult], mandatory=True) + links = wsme.wsattr({str: str}, mandatory=True) + created = wsme.wsattr(wsme.types.text, mandatory=True) + + def __init__(self, transaction_id, users, links, created): + users_result = [RegionUserResult(id=user['id'], + domain=user['domain']) for user in users] + self.users = users_result + self.transaction_id = transaction_id + self.links = links + self.created = created diff --git a/orm/services/customer_manager/scripts/db_scripts/ranger_cms_create_db.sql b/orm/services/customer_manager/scripts/db_scripts/ranger_cms_create_db.sql index 7bcba5cb..c84ab01a 100755 --- a/orm/services/customer_manager/scripts/db_scripts/ranger_cms_create_db.sql +++ b/orm/services/customer_manager/scripts/db_scripts/ranger_cms_create_db.sql @@ -175,4 +175,4 @@ create table if not exists groups_domain_role foreign key (`domain_name`) references `cms_domain` (`name`) ON DELETE CASCADE ON UPDATE NO ACTION, foreign key (`role_id`) references `groups_role` (`role_id`) ON DELETE CASCADE ON UPDATE NO ACTION, foreign key (`region_id`) references `cms_region` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION, - index role_id_idx (role_id)); + index role_id_idx (role_id)); \ No newline at end of file diff --git a/orm/tests/unit/cms/test_groups_users.py b/orm/tests/unit/cms/test_groups_users.py index 2f70a78f..62cbaa54 100644 --- a/orm/tests/unit/cms/test_groups_users.py +++ b/orm/tests/unit/cms/test_groups_users.py @@ -38,7 +38,7 @@ class TestGroupsUserController(FunctionalTest): # assert self.assertEqual(response.status_int, 200) - self.assertTrue(group_logic_mock.add_default_users.called) + self.assertTrue(group_logic_mock.add_group_default_users.called) def test_add_default_users_fail(self): # given @@ -74,13 +74,13 @@ class TestGroupsUserController(FunctionalTest): requests.delete = mock.MagicMock(return_value=ResponseMock(200)) # when - response = self.app.delete('/v1/orm/groups/{group id}/users/{user_id}') + response = self.app.delete('/v1/orm/groups/{group id}/users/{user_id}/{domain}') # assert self.assertEqual(response.status_int, 204) # uncomment below line when delete_default_user is implemented # self.assertTrue(users.utils.audit_trail.called) - self.assertTrue(group_logic_mock.delete_default_user.called) + self.assertTrue(group_logic_mock.delete_group_default_user.called) def test_delete_default_user_fail(self): # given @@ -91,7 +91,7 @@ class TestGroupsUserController(FunctionalTest): return_value=ClientSideError("blabla", 500)) # when - response = self.app.delete('/v1/orm/groups/{group id}/users/{user_id}', + response = self.app.delete('/v1/orm/groups/{group id}/users/{user_id}/{domain}', expect_errors=True) # assert @@ -106,7 +106,7 @@ class TestGroupsUserController(FunctionalTest): return_value=ClientSideError("blabla", 404)) # when - response = self.app.delete('/v1/orm/groups/{group id}/users/{user_id}', + response = self.app.delete('/v1/orm/groups/{group id}/users/{user_id}/{domain}', expect_errors=True) # assert @@ -123,16 +123,16 @@ def get_mock_group_logic(): links={}, created='1') - group_logic_mock.add_default_users.return_value = res + group_logic_mock.add_group_default_users.return_value = res elif users.GroupLogic.return_error == 1: - group_logic_mock.add_default_users.side_effect = SystemError() - group_logic_mock.delete_default_user.side_effect = SystemError() + group_logic_mock.add_group_default_users.side_effect = SystemError() + group_logic_mock.delete_group_default_user.side_effect = SystemError() else: - group_logic_mock.add_default_users.side_effect = ErrorStatus( + group_logic_mock.add_group_default_users.side_effect = ErrorStatus( status_code=404) - group_logic_mock.delete_default_user.side_effect = ErrorStatus( + group_logic_mock.delete_group_default_user.side_effect = ErrorStatus( status_code=404) return group_logic_mock @@ -145,7 +145,7 @@ class ResponseMock: GROUPS_USER_JSON = [ { - "id": "attuser1", + "id": ["attuser1"], "domain": "nc" } ] diff --git a/orm/tests/unit/ormcli/test_cmscli.py b/orm/tests/unit/ormcli/test_cmscli.py index ff633c30..dd52c105 100755 --- a/orm/tests/unit/ormcli/test_cmscli.py +++ b/orm/tests/unit/ormcli/test_cmscli.py @@ -36,6 +36,7 @@ class CmsTests(TestCase): args.groupid = 'test_groupid' args.regionid = 'test_region' args.userid = 'test_userid' + args.userdomain = 'test_userdomain' args.region = 'test_region' args.user = 'test_user' args.starts_with = 'test_startswith' @@ -99,8 +100,19 @@ class CmsTests(TestCase): 'add_group_default_users': ( requests.post, 'groups/%s/users' % args.groupid,), 'delete_group_default_user': ( - requests.delete, 'groups/%s/users/%s' % ( - args.groupid, args.userid),), + requests.delete, 'groups/%s/users/%s/%s' % ( + args.groupid, args.userid, args.userdomain),), + 'add_group_region_users': ( + requests.post, + 'groups/%s/regions/%s/users' % (args.groupid, + args.regionid,)), + 'delete_group_region_user': ( + requests.delete, + 'groups/%s/regions/%s/users/%s/%s' % (args.groupid, + args.regionid, + args.userid, + args.userdomain,)), + 'assign_group_roles': ( requests.post, 'groups/%s/roles' % args.groupid,) }