def get_exec_from_dn(ldap_entry_dn): """ Walk up the reporting structure until we get to someone who is in the Exec group. Return that someone. This function can fail to return a result if someone in the tree is in the process of leaving, i.e. they are recorded as a manager but are no longer an active account. """ # Get the membership of the Exec group. Use the mailing list so that # we get the full DNs, thus making it easier to check. _, memb_result = shared_ldap.find_group("exec", ["uniqueMember"]) members = memb_result[0].uniqueMember.values # Walk up the tree ... searching = True while searching: result = shared_ldap.get_object(ldap_entry_dn, ["manager"]) if result is not None and result.manager.value is not None: ldap_entry_dn = result.manager.value if ldap_entry_dn in members: mgr_email = shared_ldap.get_object(result.manager.value, ["mail"]) return mgr_email.mail.values[0] # otherwise loop to that person else: # The intermediate manager is leaving. searching = False return None
def get_group_owners(group_cn): """ Consistently return a list of owners. """ _, result = shared_ldap.find_group(group_cn, ["owner"]) if len(result) == 1: owners = result[0].owner.values else: owners = [] return owners
def transition(status_to, ticket_data): """ If the status is "In Progress", trigger the membership change. This status can only be reached from Open or Needs Approval. """ if status_to == "In Progress": cf_group_email_address = custom_fields.get(GROUP_EMAIL_ADDRESS) group_email_address = shared_sd.get_field( ticket_data, cf_group_email_address).strip().lower() group_email_address, result = shared_ldap.find_group( group_email_address, ['owner']) action_change(ticket_data, result[0])
def post_owners_of_group_as_comment(group_full_dn): """Emit a list of the owners of the group.""" # Need to re-fetch the group ownership because we may have changed it # since last time we queried it. name = shared_ldap.extract_id_from_dn(group_full_dn) _, result = shared_ldap.find_group(name, ["owner"]) if len(result) == 1 and result[0].owner.values != []: response = "Here are the owners for the group:\r\n" for owner in result[0].owner.values: response += "* [%s|mailto:%s]\r\n" % owner_and_display_name(owner) else: response = "There are no owners for the group." shared_sd.post_comment(response, True)
def process_public_comment(ticket_data, last_comment, keyword): """Logic to process a public comment.""" shared_sd.assign_issue_to(shared.globals.CONFIGURATION["bot_name"]) # If the original reporter IS a group owner, we will only accept comments # from the same person and those comments will be add/remove commands. # # Otherwise, deassign and let IT work on what was said. # # Get the definitive email address for the group and the owner(s). cf_group_email_address = custom_fields.get(GROUP_EMAIL_ADDRESS) group_email_address = shared_sd.get_field( ticket_data, cf_group_email_address).strip().lower() group_email_address, result = shared_ldap.find_group( group_email_address, ['owner']) # Make sure that the group still exists because this is all asynchronous # and anything could have happened! if len(result) == 0: shared_sd.post_comment( "Sorry but the group's email address can't be found in Linaro " "Login.", True) shared_sd.resolve_ticket(WONT_DO) return True if len(result) != 1: shared_sd.post_comment( "Sorry but, somehow, the group's email address appears more than " "once in Linaro Login.", True) shared_sd.resolve_ticket(WONT_DO) return True if (result[0].owner.values != [] and shared_ldap.reporter_is_group_owner(result[0].owner.values) and keyword in ("add", "remove")): grp_name = shared_ldap.extract_id_from_dn(result[0].entry_dn) changes = last_comment["body"].split("\n") batch_process_ownership_changes(grp_name, changes) post_owners_of_group_as_comment(result[0].entry_dn) return True return False
def get_group_details(ticket_data): """ Get the true email address and LDAP object for the specified group """ cf_group_email_address = custom_fields.get("Group Email Address") group_email_address = shared_sd.get_field( ticket_data, cf_group_email_address).strip().lower() return shared_ldap.find_group(group_email_address, ['owner'])
def batch_process_membership_changes(email_address, batch, auto=False, change_to_make=None): """ Process a list of changes to the membership """ # If auto is false, we're expecting "keyword emailaddress" and will stop on # the first line that doesn't match that syntax. If auto is true, we're # just expecting emailaddress and the change flag will indicate add or # remove. # # The formatting of the text varies from "\r\n" in the original request to # "\n" in comments, so the *caller* must pass batch as a list. change_made = False # We need a list of current members to sanity check the request. _, result = shared_ldap.find_group(email_address, ["uniqueMember"]) if len(result) == 1 and "uniqueMember" in result[0]: members = result[0].uniqueMember.values else: members = [] group_cn = shared_ldap.extract_id_from_dn(result[0].entry_dn) response = "" for change in batch: # When submitting a comment via the portal, blank lines get inserted # so we just ignore them. if change != "": email_address, keyword = evaluate_change(change, auto, change_to_make) if keyword is None: response += ( "\r\nCouldn't find a command at the start of '%s'. " "*Processing of this request will now stop.*\r\n" % change) break result = find_member_change(email_address) if keyword == "add": if result is None: response += ( "Couldn't find an entry '%s' on Linaro Login. Please " "use https://servicedesk.linaro.org/servicedesk/" "customer/portal/3/create/120 to create a contact " "(email only) or external account (if login required) " "and then submit a new ticket to add them.\r\n" % email_address) elif result == ("cn=%s,ou=mailing,ou=groups,dc=linaro,dc=org" % group_cn): response += ( "You cannot add the group as a member to itself.\r\n") elif result in members: response += ("%s is already a member of the group.\r\n" % email_address) else: response += "Adding %s\r\n" % email_address shared_ldap.add_to_group(group_cn, result) members.append(result) change_made = True elif keyword == "remove": if result is None: response += ( "Couldn't find an entry '%s' on Linaro Login. Did you " "mistype?\r\n" % email_address) elif result in members: response += "Removing %s\r\n" % email_address shared_ldap.remove_from_group(group_cn, result) members.remove(result) change_made = True else: response += ( "%s is not a member of the group so cannot be " "removed as one.\r\n" % email_address) else: response += ("%s is not recognised as 'add' or 'remove'.\r\n" % keyword) if change_made: linaro_shared.trigger_google_sync() response += ( "Please note it can take up to 15 minutes for these changes to " "appear on Google.") if response != "": shared_sd.post_comment(response, True)
def create(ticket_data): """Triggered when the issue is created.""" cf_group_email_address = custom_fields.get(GROUP_EMAIL_ADDRESS) group_email_address = shared_sd.get_field( ticket_data, cf_group_email_address).strip().lower() group_email_address, result = shared_ldap.find_group( group_email_address, ['owner']) shared_sd.set_summary( "View/Change group ownership for %s" % group_email_address) shared_sd.assign_issue_to(shared.globals.CONFIGURATION["bot_name"]) if len(result) == 0: shared_sd.post_comment( "Sorry but the group's email address can't be found in Linaro " "Login.", True) shared_sd.resolve_ticket(WONT_DO) return if len(result) != 1: shared_sd.post_comment( "Sorry but, somehow, the group's email address appears more than " "once in Linaro Login.", True) shared_sd.resolve_ticket(WONT_DO) return # See if the bot owns this group owners = result[0].owner.values if (len(owners) == 1 and owners[0] == IT_BOT): shared_sd.post_comment( ( "This group is maintained through automation. It is not " "possible to change the owners of this group or raise " "tickets to directly change the membership. If you want " "to understand how this group is maintained automatically, " "please raise a general IT Services support ticket." ), True ) shared_sd.resolve_ticket() return # Do we have any changes to process? If not, post the current owners to # the ticket. cf_group_owners = custom_fields.get("Group Owners") ownerchanges = shared_sd.get_field(ticket_data, cf_group_owners) if ownerchanges is None: post_owners_of_group_as_comment(result[0].entry_dn) if shared_ldap.reporter_is_group_owner(result[0].owner.values): shared_sd.post_comment( ("As you are an owner of this group, you can make changes to " "the ownership by posting new comments to this ticket with " "the following format:\r\n" "*add* <email address>\r\n" "*remove* <email address>\r\n" "One command per line but you can have multiple changes in a " "single comment. If you do not get the syntax right, the " "automation will not be able to understand your request and " "processing will stop.\r\n"), True) shared_sd.transition_request_to("Waiting for customer") else: shared_sd.post_comment( "As you are not an owner of this group, if you want to make " "changes to the ownership, you will need to open a " "[new ticket|https://servicedesk.linaro.org/servicedesk/" "customer/portal/3/create/140].", True) shared_sd.resolve_ticket() return # There are changes ... but is the requester a group owner? cf_approvers = custom_fields.get("Approvers") if result[0].owner.values == []: # No owners at all. IT is always allowed to make changes if shared_ldap.is_user_in_group("its", shared.globals.REPORTER): shared_sd.transition_request_to("In progress") else: shared_sd.post_comment( "This group has no owners. Asking IT Services to review " "your request.", True) it_members = shared_ldap.get_group_membership( "cn=its,ou=mailing,ou=groups,dc=linaro,dc=org") shared_sd.assign_approvers(it_members, cf_approvers) shared_sd.transition_request_to("Needs approval") elif shared_ldap.reporter_is_group_owner(result[0].owner.values): shared_sd.transition_request_to("In progress") else: shared_sd.post_comment( "As you are not an owner of this group, the owners will be " "asked to approve or decline your request.", True) shared_sd.assign_approvers(result[0].owner.values, cf_approvers) shared_sd.transition_request_to("Needs approval")