class BofhdExtension(BofhdCommonMethods): """ Commands for getting, setting and unsetting consent. """ hidden_commands = {} # Not accessible through bofh all_commands = {} parent_commands = False authz = ConsentAuth def __init__(self, *args, **kwargs): """ """ super(BofhdExtension, self).__init__(*args, **kwargs) # POST: for attr in ('ConsentType', 'EntityConsent'): if not hasattr(self.const, attr): raise RuntimeError('consent: Missing consent constant types') @classmethod def get_help_strings(cls): """ Help strings for consent commands. """ group, cmd, args = get_help_strings() group.setdefault('consent', 'Commands for handling consents') cmd.setdefault('consent', dict()).update({ 'consent_set': cls.consent_set.__doc__, 'consent_unset': cls.consent_unset.__doc__, 'consent_info': cls.consent_info.__doc__, 'consent_list': cls.consent_list.__doc__, }) args.update({ 'consent_type': [ 'type', 'Enter consent type', "'consent list' lists defined consents" ], }) return (group, cmd, args) def check_consent_support(self, entity): """ Assert that entity has EntityConsentMixin. :param Cerebrum.Entity entity: The entity to check. :raise NotImplementedError: If entity lacks consent support. """ entity_type = self.const.EntityType(entity.entity_type) if not isinstance(entity, EntityConsentMixin): raise NotImplementedError( "Entity type '%s' does not support consent." % six.text_type(entity_type)) def _get_consent(self, consent_ident): u""" Get consent constant from constant strval or intval. :type consent_ident: int, str, Cerebrum.Constant.ConstantCode :param consent_ident: Something to lookup a consent constant by :return tuple: A tuple consisting of a EntityConsent constant and its ConsentType constant. :raise Cerebrum.Error.NotFoundError: If the constant cannot be found. """ consent = self.const.human2constant( consent_ident, const_type=self.const.EntityConsent) if not consent: raise CerebrumError("No consent %r" % consent_ident) consent_type = self.const.ConsentType(consent.consent_type) return (consent, consent_type) # # consent set <ident> <consent_type> # all_commands['consent_set'] = Command( ('consent', 'set'), Id(help_ref="id:target:account"), ConsentType(), fs=FormatSuggestion( "OK: Set consent '%s' (%s) for %s '%s' (entity_id=%s)", ('consent_name', 'consent_type', 'entity_type', 'entity_name', 'entity_id')), perm_filter='can_set_consent') def consent_set(self, operator, entity_ident, consent_ident): """ Set a consent for an entity. """ entity = self.util.get_target(entity_ident, restrict_to=[]) self.ba.can_set_consent(operator.get_entity_id(), entity) self.check_consent_support(entity) consent, consent_type = self._get_consent(consent_ident) entity_name = self._get_entity_name(entity.entity_id, entity.entity_type) entity.set_consent(consent) entity.write_db() return { 'consent_name': six.text_type(consent), 'consent_type': six.text_type(consent_type), 'entity_id': entity.entity_id, 'entity_type': six.text_type(self.const.EntityType(entity.entity_type)), 'entity_name': entity_name, } # # consent unset <ident> <consent_type> # all_commands['consent_unset'] = Command( ('consent', 'unset'), Id(help_ref="id:target:account"), ConsentType(), fs=FormatSuggestion( "OK: Removed consent '%s' (%s) for %s '%s' (entity_id=%s)", ('consent_name', 'consent_type', 'entity_type', 'entity_name', 'entity_id')), perm_filter='can_unset_consent') def consent_unset(self, operator, entity_ident, consent_ident): """ Remove a previously set consent. """ entity = self.util.get_target(entity_ident, restrict_to=[]) self.ba.can_unset_consent(operator.get_entity_id(), entity) self.check_consent_support(entity) consent, consent_type = self._get_consent(consent_ident) entity_name = self._get_entity_name(entity.entity_id, entity.entity_type) entity.remove_consent(consent) entity.write_db() return { 'consent_name': six.text_type(consent), 'consent_type': six.text_type(consent_type), 'entity_id': entity.entity_id, 'entity_type': six.text_type(self.const.EntityType(entity.entity_type)), 'entity_name': entity_name, } # # consent info <ident> # all_commands['consent_info'] = Command( ('consent', 'info'), Id(help_ref="id:target:account"), fs=FormatSuggestion( '%-15s %-8s %-17s %-17s %s', ('consent_name', 'consent_type', format_datetime('consent_time_set'), format_datetime('consent_time_expire'), 'consent_description'), hdr='%-15s %-8s %-17s %-17s %s' % ('Name', 'Type', 'Set at', 'Expires at', 'Description')), perm_filter='can_show_consent_info') def consent_info(self, operator, ident): u""" View all set consents for a given entity. """ entity = self.util.get_target(ident, restrict_to=[]) self.check_consent_support(entity) self.ba.can_show_consent_info(operator.get_entity_id(), entity) consents = [] for row in entity.list_consents(entity_id=entity.entity_id, filter_expired=False): consent, consent_type = self._get_consent(int(row['consent_code'])) consents.append({ 'consent_name': six.text_type(consent), 'consent_type': six.text_type(consent_type), 'consent_time_set': row['time_set'], 'consent_time_expire': row['expiry'], 'consent_description': row['description'], }) if not consents: name = self._get_entity_name(entity.entity_id, entity.entity_type) raise CerebrumError( "'%s' (entity_type=%s, entity_id=%s) has no consents set" % (name, six.text_type(self.const.EntityType( entity.entity_type)), entity.entity_id)) return consents # # consent list # all_commands['consent_list'] = Command( ('consent', 'list'), fs=FormatSuggestion( '%-15s %-8s %s', ('consent_name', 'consent_type', 'consent_description'), hdr='%-16s %-9s %s' % ('Name', 'Type', 'Description')), perm_filter='can_list_consents') def consent_list(self, operator): """ List all consent types. """ self.ba.can_list_consents(operator.get_entity_id()) consents = [] for consent in self.const.fetch_constants(self.const.EntityConsent): consent, consent_type = self._get_consent(int(consent)) consents.append({ 'consent_name': six.text_type(consent), 'consent_type': six.text_type(consent_type), 'consent_description': consent.description, }) if not consents: raise CerebrumError("No consent types defined yet") return consents
class BofhdExtension(BofhdCommandBase): all_commands = {} hidden_commands = {} authz = RequestsAuth # # misc change_request <request-id> <datetime> # all_commands['misc_change_request'] = Command(("misc", "change_request"), Id(help_ref="id:request_id"), DateTimeString()) def misc_change_request(self, operator, request_id, datetime): if not request_id: raise CerebrumError('Request id required') if not datetime: raise CerebrumError('Date required') datetime = self._parse_date(datetime) br = BofhdRequests(self.db, self.const) old_req = br.get_requests(request_id=request_id) if not old_req: raise CerebrumError("There is no request with id=%r" % request_id) else: # If there is anything, it's at most one old_req = old_req[0] # If you are allowed to cancel a request, you can change it :) self.ba.can_cancel_request(operator.get_entity_id(), request_id) br.delete_request(request_id=request_id) br.add_request(operator.get_entity_id(), datetime, old_req['operation'], old_req['entity_id'], old_req['destination_id'], old_req['state_data']) return "OK, altered request %s" % request_id # # misc list_bofhd_request_types # all_commands['misc_list_bofhd_request_types'] = Command( ("misc", "list_bofhd_request_types"), fs=FormatSuggestion("%-20s %s", ("code_str", "description"), hdr="%-20s %s" % ("Code", "Description"))) def misc_list_bofhd_request_types(self, operator): br = BofhdRequests(self.db, self.const) result = [] for row in br.get_operations(): br_code = self.const.BofhdRequestOp(row['code_str']) result.append({ 'code_str': six.text_type(br_code).lstrip('br_'), 'description': br_code.description, }) return result # # misc cancel_request # all_commands['misc_cancel_request'] = Command( ("misc", "cancel_request"), SimpleString(help_ref='id:request_id')) def misc_cancel_request(self, operator, req): if req.isdigit(): req_id = int(req) else: raise CerebrumError("Request-ID must be a number") br = BofhdRequests(self.db, self.const) if not br.get_requests(request_id=req_id): raise CerebrumError("Request ID %d not found" % req_id) self.ba.can_cancel_request(operator.get_entity_id(), req_id) br.delete_request(request_id=req_id) return "OK, %d canceled" % req_id # # misc list_requests # all_commands['misc_list_requests'] = Command( ("misc", "list_requests"), SimpleString(help_ref='string_bofh_request_search_by', default='requestee'), SimpleString(help_ref='string_bofh_request_target', default='<me>'), fs=FormatSuggestion("%-7i %-10s %-16s %-16s %-10s %-20s %s", ("id", "requestee", format_time("when"), "op", "entity", "destination", "args"), hdr="%-7s %-10s %-16s %-16s %-10s %-20s %s" % ("Id", "Requestee", "When", "Op", "Entity", "Destination", "Arguments"))) def misc_list_requests(self, operator, search_by, destination): br = BofhdRequests(self.db, self.const) ret = [] if destination == '<me>': destination = self._get_account(operator.get_entity_id(), idtype='id') destination = destination.account_name if search_by == 'requestee': account = self._get_account(destination) rows = br.get_requests(operator_id=account.entity_id, given=True) elif search_by == 'operation': try: destination = int( self.const.BofhdRequestOp('br_' + destination)) except Errors.NotFoundError: raise CerebrumError("Unknown request operation %s" % destination) rows = br.get_requests(operation=destination) elif search_by == 'disk': disk_id = self._get_disk(destination)[1] rows = br.get_requests(destination_id=disk_id) elif search_by == 'account': account = self._get_account(destination) rows = br.get_requests(entity_id=account.entity_id) else: raise CerebrumError("Unknown search_by criteria") for r in rows: op = self.const.BofhdRequestOp(r['operation']) dest = None ent_name = None if op in (self.const.bofh_move_user, self.const.bofh_move_request, self.const.bofh_move_user_now): disk = self._get_disk(r['destination_id'])[0] dest = disk.path elif op in (self.const.bofh_move_give, ): dest = self._get_entity_name(r['destination_id'], self.const.entity_group) elif op in (self.const.bofh_email_create, self.const.bofh_email_delete): dest = self._get_entity_name(r['destination_id'], self.const.entity_host) elif op in (self.const.bofh_sympa_create, self.const.bofh_sympa_remove): ea = Email.EmailAddress(self.db) if r['destination_id'] is not None: ea.find(r['destination_id']) dest = ea.get_address() ea.clear() try: ea.find(r['entity_id']) except Errors.NotFoundError: ent_name = "<not found>" else: ent_name = ea.get_address() if ent_name is None: ent_name = self._get_entity_name(r['entity_id'], self.const.entity_account) if r['requestee_id'] is None: requestee = '' else: requestee = self._get_entity_name(r['requestee_id'], self.const.entity_account) ret.append({ 'when': r['run_at'], 'requestee': requestee, 'op': six.text_type(op), 'entity': ent_name, 'destination': dest, 'args': r['state_data'], 'id': r['request_id'] }) ret.sort(lambda a, b: cmp(a['id'], b['id'])) return ret
class BofhdExtension(BofhdCommandBase): """The BofhdExctension for AD related commands and functionality.""" all_commands = {} parent_commands = False authz = BofhdAuth @classmethod def get_help_strings(cls): group_help = { 'ad': "Commands for AD related functionality", } # The texts in command_help are automatically line-wrapped, and should # not contain \n command_help = { 'ad': { 'ad_attributetypes': 'List out all defined AD-attribute types', 'ad_list_attributes': 'List all attributes related to given spread', 'ad_info': 'Get AD-related information about an entity', 'ad_set_attribute': 'Set a given AD-attribute for an entity', 'ad_remove_attribute': 'Remove a given AD-attribute for an entity', }, } arg_help = { 'attr_type': ['attr_type', 'Type of AD-attribute', 'See "ad attributetypes" for a list of defined types'], 'attr_value': ['value', 'Value of AD-attribute', 'A string value for the AD-attribute'], 'id': ['id', 'Id', 'The identity of an entity'], 'spread': ['spread', 'Spread', 'The spread for the attribute'], } return (group_help, command_help, arg_help) # ad_attributetypes all_commands['ad_attributetypes'] = Command( ("ad", "attributetypes"), fs=FormatSuggestion( "%-14s %-8s %s", ('name', 'multi', 'desc'), hdr="%-14s %-8s %s" % ('Name', 'Multival', 'Description') ) ) def ad_attributetypes(self, operator): """List out all types of AD-attributes defined in Cerebrum.""" return [ { 'name': six.text_type(c), 'multi': c.multivalued, 'desc': c.description, } for c in self.const.fetch_constants(self.const.ADAttribute) ] # # ad_list_attributes # all_commands['ad_list_attributes'] = Command( ("ad", "list_attributes"), AttributeType(optional=True), Spread(optional=True), fs=FormatSuggestion( "%-20s %-20s %-20s %s", ('attr_type', 'spread', 'entity', 'value'), hdr="%-20s %-20s %-20s %s" % ('Attribute', 'Spread', 'Entity', 'Value') ), perm_filter='is_superuser' ) # TODO: fix BA! def ad_list_attributes(self, operator, attr_type=None, spread=None): """List all attributes, limited to given input.""" if not self.ba.is_superuser(operator.get_entity_id()): raise PermissionDenied("Only for superusers, for now") # TODO: check if operator has access to the entity atr = spr = None if attr_type: atr = _get_attr(self.const, attr_type) if spread: spr = _get_spread(self.const, spread) ent = EntityADMixin(self.db) return [ { 'attr_type': six.text_type( self.const.ADAttribute(row['attr_code'])), 'spread': six.text_type( self.const.Spread(row['spread_code'])), 'entity': self._get_entity_name(row['entity_id']), 'value': row['value'], } for row in ent.list_ad_attributes(spread=spr, attribute=atr) ] # # ad_info # all_commands['ad_info'] = Command( ("ad", "info"), EntityType(), Id(), fs=FormatSuggestion([ ('AD-id: %-12s %s', ('id_type', 'ad_id')), ('%-20s %-20s %s', ('spread', 'attr_type', 'attr_value'), '%-20s %-20s %s' % ('Spread', 'Attribute', 'Value')), ]), perm_filter='is_superuser' ) def ad_info(self, operator, entity_type, ident): """Return AD related information about a given entity.""" if not self.ba.is_superuser(operator.get_entity_id()): raise PermissionDenied("Only for superusers, for now") ent = self._get_entity(entity_type, ident) # TODO: check if operator has access to the entity? ret = [] for row in ent.search_external_ids( source_system=self.const.system_ad, entity_id=ent.entity_id, fetchall=False): ret.append({ 'id_type': six.text_type( self.const.EntityExternalId(row['id_type'])), 'ad_id': row['external_id'], }) for row in ent.list_ad_attributes(entity_id=ent.entity_id): ret.append({ 'attr_type': six.text_type( self.const.ADAttribute(row['attr_code'])), 'spread': six.text_type( self.const.Spread(row['spread_code'])), 'attr_value': row['value'], }) return ret # # ad_set_attribute <entity-type> <entity-id> <attr> <spread> <value> # all_commands['ad_set_attribute'] = Command( ("ad", "set_attribute"), EntityType(), Id(), AttributeType(), Spread(), SimpleString(help_ref='attr_value'), fs=FormatSuggestion([ ("AD-attribute %s set for %s, limited to spread %s: %s", ('attribute', 'entity_name', 'spread', 'value')), ('WARNING: %s', ('warning', )) ]), perm_filter='is_superuser' ) # TODO: fix BA! def ad_set_attribute(self, operator, entity_type, ident, attr_type, spread, value): """Set an attribute for a given entity.""" if not self.ba.is_superuser(operator.get_entity_id()): raise PermissionDenied("Only for superusers, for now") # TODO: check if operator has access to the entity ent = self._get_entity(entity_type, ident) atr = _get_attr(self.const, attr_type) spr = _get_spread(self.const, spread) ent.set_ad_attribute(spread=spr, attribute=atr, value=value) ent.write_db() # We keep using the constant strvals: spread = six.text_type(spr) attr_type = six.text_type(atr) # Check if the spread and attribute is defined for an AD-sync. If not, # add a warning to the output. retval = [{ 'attribute': attr_type, 'entity_name': self._get_entity_name(ent.entity_id), 'entity_type': six.text_type(ent.entity_type), 'spread': spread, 'value': value, }] config = getattr(cereconf, 'AD_SPREADS', None) if config: if spread not in config: retval.append({ 'warning': 'No AD-sync defined for spread: %s' % spread, }) elif attr_type not in config[spread].get('attributes', ()): retval.append({ 'warning': 'AD-sync for %s does not know of: %s' % ( attr_type, spread), }) return retval # # ad_remove_attribute <entity-type> <entity-id> <attr> <spread> # all_commands['ad_remove_attribute'] = Command( ("ad", "remove_attribute"), EntityType(), Id(), AttributeType(), Spread(), fs=FormatSuggestion( "AD-attribute %s removed for %s, for spread %s", ('attribute', 'entity_name', 'spread') ), perm_filter='is_superuser' ) # TODO: fix BA! def ad_remove_attribute(self, operator, entity_type, id, attr_type, spread): """Remove an AD-attribute for a given entity.""" if not self.ba.is_superuser(operator.get_entity_id()): raise PermissionDenied("Only for superusers, for now") ent = self._get_entity(entity_type, id) atr = _get_attr(self.const, attr_type) spr = _get_spread(self.const, spread) try: ent.delete_ad_attribute(spread=spr, attribute=atr) except Errors.NotFoundError: raise CerebrumError( '%s does not have AD-attribute %s with spread %s' % (ent.entity_id, six.text_type(atr), six.text_type(spr)) ) ent.write_db() return { 'attribute': six.text_type(atr), 'entity_name': self._get_entity_name(ent.entity_id), 'entity_type': six.text_type(ent.entity_type), 'spread': six.text_type(spr), }
class BofhdHistoryCmds(BofhdCommandBase): """BofhdExtension for history related commands and functionality.""" all_commands = {} authz = BofhdHistoryAuth @property def util(self): try: return self.__util except AttributeError: self.__util = BofhdUtils(self.db) return self.__util @classmethod def get_help_strings(cls): """Get help strings.""" group_help = { 'history': "History related commands", } command_help = { 'history': { 'history_show': 'List changes made to an entity', } } argument_help = { 'limit_number_of_results': [ 'limit', 'Number of changes to list', 'Upper limit for how many changes to include, counting ' 'backwards from the most recent. Default (when left empty) ' 'is 0, which means no limit' ], 'yes_no_all_changes': [ 'all', 'All involved changes?', 'List all changes where the entity is involved (yes), or ' 'only the ones where the entity itself is changed (no)' ], } return merge_help_strings( get_help_strings(), (group_help, command_help, argument_help), ) # # history show <entity> [yes|no] [n] # default_any_entity = 'yes' default_num_changes = '0' # 0 means no limit all_commands['history_show'] = Command( ('history', 'show'), Id(help_ref='id:target:entity'), YesNo(help_ref='yes_no_all_changes', optional=True, default=default_any_entity), Integer(help_ref='limit_number_of_results', optional=True, default=default_num_changes), fs=FormatSuggestion('%s [%s]: %s', ('timestamp', 'change_by', 'message')), perm_filter='can_show_history', ) def history_show(self, operator, entity, any_entity=default_any_entity, limit_number_of_results=default_num_changes): """ show audit log for a given entity. """ ent = self.util.get_target(entity, restrict_to=[]) self.ba.can_show_history(operator.get_entity_id(), ent) ret = [] try: n = int(limit_number_of_results) except ValueError: raise CerebrumError('Illegal range limit, must be an integer: ' '{}'.format(limit_number_of_results)) record_db = AuditLogAccessor(self.db) rows = list(record_db.search(entities=ent.entity_id)) if self._get_boolean(any_entity): rows.extend(list(record_db.search(targets=ent.entity_id))) rows = sorted(rows, key=lambda r: (r.timestamp, r.record_id)) _process = AuditRecordProcessor() for r in rows[-n:]: processed_row = _process(r) ret.append({ 'timestamp': processed_row.timestamp, 'change_by': processed_row.change_by, 'message': processed_row.message }) return ret
class BofhdExtension(BofhdCommonMethods): u""" Extends bofhd with a 'note' command group. """ all_commands = {} parent_commands = False authz = BofhdAuth @classmethod def get_help_strings(cls): group_help = { 'note': 'Entity note related commands', } command_help = { 'note': { 'note_show': 'Show notes associated with an entity', 'note_add': 'Add a new note to an entity', 'note_remove': 'Remove a note associated with an entity', }, } arg_help = { 'note_id': ['note_id', 'Enter note ID', 'Enter the ID of the note'], 'note_subject': ['note_subject', 'Enter subject', 'Enter the subject of the note'], 'note_description': ['note_description', 'Enter description', 'Enter the description of the note'], } return (group_help, command_help, arg_help) # # note show <subject-id> # all_commands['note_show'] = Command( ('note', 'show'), Id(help_ref='id:target:entity'), fs=FormatSuggestion([ ("%d note(s) found for %s:\n", ("notes_total", "entity_target")), ("Note #%d added by %s on %s:\n" "%s: %s\n", ("note_id", "creator", format_time("create_date"), "subject", "description")) ]), perm_filter='can_show_notes') def note_show(self, operator, entity_target): self.ba.can_show_notes(operator.get_entity_id()) entity = self.util.get_target( entity_target, default_lookup="account", restrict_to=[] ) enote = Note.EntityNote(self.db) enote.find(entity.entity_id) notes = enote.get_notes() result = [] if len(notes) is 0: return "No notes were found for %s" % (entity_target) else: result.append({ 'notes_total': len(notes), 'entity_target': entity_target, }) for note_row in notes: note = {} for key, value in note_row.items(): note[key] = value if key in ('subject', 'description') and len(value) is 0: note[key] = '<not set>' # translate creator_id to username acc = self._get_account(note_row['creator_id'], idtype='id') note['creator'] = acc.account_name result.append(note) return result # # note add <subject-id> <title> <contents> # all_commands['note_add'] = Command( ('note', 'add'), Id(help_ref='id:target:entity'), SimpleString(help_ref='note_subject'), SimpleString(help_ref='note_description'), perm_filter='can_add_notes') def note_add(self, operator, entity_target, subject, description): self.ba.can_add_notes(operator.get_entity_id()) if len(subject) > 70: raise CerebrumError( u"Subject field cannot be longer than 70 characters") if len(description) > 1024: raise CerebrumError( u"Description field cannot be longer than 1024 characters") entity = self.util.get_target(entity_target, restrict_to=[]) enote = Note.EntityNote(self.db) enote.find(entity.entity_id) note_id = enote.add_note(operator.get_entity_id(), subject, description) return "Note #%s was added to entity %s" % (note_id, entity_target) # # note remove <subject-id> <note-id> # all_commands['note_remove'] = Command( ('note', 'remove'), Id(help_ref='id:target:entity'), SimpleString(help_ref='note_id'), perm_filter='can_remove_notes') def note_remove(self, operator, entity_target, note_id): self.ba.can_remove_notes(operator.get_entity_id()) entity = self.util.get_target(entity_target, restrict_to=[]) enote = Note.EntityNote(self.db) enote.find(entity.entity_id) if not note_id.isdigit(): raise CerebrumError("Note ID must be a numeric value") for note_row in enote.get_notes(): if int(note_row['note_id']) is int(note_id): enote.delete_note(note_id) return "Note #%s associated with entity %s was removed" % ( note_id, entity_target) raise CerebrumError("Note #%s is not associated with entity %s" % ( note_id, entity_target))