def action_group_membership_change(email_address, group_obj, ticket_data): """ Apply the membership changes from the ticket """ cf_group_member_email_addresses = custom_fields.get( "Group member email addresses") changes = shared_sd.get_field(ticket_data, cf_group_member_email_addresses) changes = linaro_shared.response_split(changes) cf_add_remove = custom_fields.get("Added / Removed") change_to_make = shared_sd.get_field(ticket_data, cf_add_remove)["value"] batch_process_membership_changes(email_address, changes, True, change_to_make) # Need to check if the requester is a group owner ... if "owner" in group_obj and shared_ldap.reporter_is_group_owner( group_obj.owner.values): shared_sd.post_comment( ("As you are an owner of this group, you can make further changes" " to the membership 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") # This shouldn't be # necessary as comments from the assignee should trigger the # transition but it isn't. else: shared_sd.resolve_ticket()
def action_change(ticket_data, group_owners): """ Process the ownership changes specified in the field. """ grp_name = shared_ldap.extract_id_from_dn(group_owners.entry_dn) cf_group_owners = custom_fields.get("Group Owners") ownerchanges = shared_sd.get_field(ticket_data, cf_group_owners) changes = ownerchanges.split("\r\n") cf_added_removed = custom_fields.get("Added / Removed") action_value = shared_sd.get_field(ticket_data, cf_added_removed)["value"] if action_value is None: change_to_make = "" else: change_to_make = action_value batch_process_ownership_changes(grp_name, changes, True, change_to_make) post_owners_of_group_as_comment(group_owners.entry_dn) if (group_owners.owner.values != [] and shared_ldap.reporter_is_group_owner(group_owners.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.resolve_ticket()
def ok_to_proceed(email_address): """ Enforce company policy rules. """ # Is the email address already present in LDAP? check = shared_ldap.find_from_email(email_address) if check is None: check = shared_ldap.find_from_attribute("cn", email_address) if check is not None: response = ("Cannot fulfil this request because the email address is " "already being used by %s" % check) shared_sd.post_comment(response, True) shared_sd.resolve_ticket(WONT_DO) return False check = shared_ldap.find_from_attribute("passwordSelfResetBackupMail", email_address) if check is not None: dup_email = shared_ldap.get_object(check, ["mail"]) if dup_email.mail.values != []: dup_email = dup_email.mail.values[0] else: # No email address so provide the DN instead dup_email = check response = ("Cannot fulfil this request because there is a Linaro " "account associated with the email address (%s)" % dup_email) shared_sd.post_comment(response, True) shared_sd.resolve_ticket(WONT_DO) return False org_unit = shared_ldap.find_best_ou_for_email(email_address) if org_unit == "ou=staff,ou=accounts,dc=linaro,dc=org": shared_sd.post_comment( "Cannot fulfil this request because the email address is " "reserved for Linaro staff.", True) shared_sd.resolve_ticket(WONT_DO) return False # Who is asking for this account? If staff, they can create any account. # If not, the OU must match. reporter_ou = shared_ldap.find_best_ou_for_email(shared.globals.REPORTER) if reporter_ou != "ou=staff,ou=accounts,dc=linaro,dc=org": if org_unit == "ou=the-rest,ou=accounts,dc=linaro,dc=org": shared_sd.post_comment( "Only Linaro staff and Linaro Members can create additional accounts.", True) shared_sd.resolve_ticket(WONT_DO) return False if reporter_ou != org_unit: shared_sd.post_comment( "Cannot fulfil this request because you can " "only create accounts/contacts for your own organisation.", True) shared_sd.resolve_ticket(WONT_DO) return False return True
def group_sanity_check(ldap_obj): """ Check that we've got one and only one object """ if len(ldap_obj) == 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 False if len(ldap_obj) != 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 False return True
def create(ticket_data): """ Ticket creation handler. """ cf_email_address = custom_fields.get("Email Address") email_address = shared_sd.get_field(ticket_data, cf_email_address) if email_address is None: # It shouldn't be - it is a mandatory field ... shared_sd.post_comment( "It has not been possible to create the account as requested.", True) shared_sd.post_comment( "Unable to retrieve email address from CF %s" % cf_email_address, False) shared_sd.resolve_ticket("Declined") return email_address = email_address.strip().lower() email_address = shared_ldap.cleanup_if_gmail(email_address) shared_sd.set_summary("Create external user/account for %s" % email_address) if not ok_to_proceed(email_address): return cf_first_name = custom_fields.get("First Name") cf_family_name = custom_fields.get("Family Name") first_name = shared_sd.get_field(ticket_data, cf_first_name) if first_name is not None: first_name = first_name.strip() surname = shared_sd.get_field(ticket_data, cf_family_name).strip() uid = shared_ldap.calculate_uid(first_name, surname) if uid is None: shared_sd.post_comment( "It has not been possible to create the account as requested.", True) shared_sd.post_comment( "Cannot calculated UID for '%s' '%s'" % (first_name, surname), False) shared_sd.resolve_ticket("Declined") return md5_password = None cf_account_type = custom_fields.get("External Account / Contact") account_type = shared_sd.get_field(ticket_data, cf_account_type) if account_type != "Contact": _, md5_password = linaro_shared.make_password() account_dn = shared_ldap.create_account(first_name, surname, email_address, md5_password) if account_dn is None: shared_sd.post_comment( "Sorry but something went wrong while creating the entry", True) shared_sd.transition_request_to("Waiting for support") shared_sd.assign_issue_to(None) return if account_type != "Contact": send_new_account_email(first_name, surname, email_address, account_dn) shared_sd.resolve_ticket()
def create(ticket_data): """ Triggered when the issue is created """ cf_approvers = custom_fields.get("Approvers") group_email_address, result = get_group_details(ticket_data) shared_sd.set_summary("Add/Remove group members for %s" % group_email_address) shared_sd.assign_issue_to(shared.globals.CONFIGURATION["bot_name"]) if not group_sanity_check(result): return group_obj = result[0] if "owner" not in group_obj: if shared_ldap.is_user_in_group("its", shared.globals.REPORTER): shared_sd.transition_request_to("In progress") return 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) return if IT_BOT in group_obj.owner.values: shared_sd.post_comment( "Sorry but the membership of this group is maintained" " automatically.", True) shared_sd.resolve_ticket(WONT_DO) return if shared_ldap.reporter_is_group_owner(group_obj.owner.values): shared_sd.transition_request_to("In progress") return 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(group_obj.owner.values, cf_approvers)
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 create(ticket_data): """ Triggered when the issue is created """ shared_sd.assign_issue_to(shared.globals.CONFIGURATION["bot_name"]) # Keep the linter happy _ = ticket_data # Need to get all of the groups, with their owners all_groups = shared_ldap.find_matching_objects( "(objectClass=groupOfUniqueNames)", ["owner", "displayName", "cn", "uniqueMember"] ) owned_groups = [] for group in all_groups: owners = group.owner.values if shared_ldap.reporter_is_group_owner(owners): owned_groups.append(group) if owned_groups == []: shared_sd.post_comment( "You do not appear to be the owner of any " "groups on Linaro Login.", True ) shared_sd.resolve_ticket() return owned_groups = sorted(owned_groups, key=group_name_lower) response = ( "Below are the groups you can manage.\n\n" "There are automated Service Desk requests for [changing the " "membership of a group|https://servicedesk.linaro.org/servicedesk" "/customer/portal/3/create/139] and [changing the owners of a " "group|https://servicedesk.linaro.org/servicedesk/customer/portal" "/3/create/140].\n\n" ) for group in owned_groups: empty = check_if_group_has_members(group) response += "* %s%s\n" % (group_name(group), empty) shared_sd.post_comment(response, True) shared_sd.resolve_ticket()
def create(ticket_data): """ Create handler. """ # Start by making sure that the requester is in IT or HR. email_address = shared_sd.reporter_email_address(ticket_data) account_dn = shared_ldap.find_from_email(email_address) valid_account = shared_ldap.is_dn_in_group("hr", account_dn) or \ shared_ldap.is_dn_in_group("it-services", account_dn) if not valid_account: shared_sd.post_comment( "You must be in HR or IT Services to use this request.", True) shared_sd.resolve_ticket(resolution_state="Won't Do") return outcome = RESULT_STATE.Done cf_addresses = custom_fields.get("Email Address(s) of Users") addresses = shared_sd.get_field(ticket_data, cf_addresses).split("\r\n") for address in addresses: # Clean up by trimming white space. clean = address.strip().lower() if clean != "": result = transition_user_account(clean) if (result == RESULT_STATE.IT or (result == RESULT_STATE.Customer and outcome == RESULT_STATE.Done)): outcome = result # Did all of the accounts transition? if outcome == RESULT_STATE.Done: shared_sd.resolve_ticket() elif outcome == RESULT_STATE.Customer: shared_sd.post_comment( "Sorry but it has not been possible to fully process your request. " "Hopefully the above comments are helpful. Further replies to this " "ticket will be handled by IT Services staff rather than the automation bot.", True) shared_sd.assign_issue_to(None) else: shared_sd.transition_request_to("Waiting for Support")
def create(ticket_data): """ Create handler. """ # There aren't any fields in the form for us to process. This # is a simple case of checking that the requestor is a member of # staff and then adding them to the group that controls SSH access # to the system. email_address = shared_sd.reporter_email_address(ticket_data) account_dn = shared_ldap.find_from_email(email_address) valid_account = shared_ldap.is_dn_in_group("employees", account_dn) or \ shared_ldap.is_dn_in_group("assignees", account_dn) if not valid_account: shared_sd.post_comment( "You must be a Linaro employee or assignee to use the " "hackbox2 service.", True) shared_sd.resolve_ticket(resolution_state="Won't Do") return if shared_ldap.is_dn_in_group("hackbox-users", account_dn): shared_sd.post_comment( "You appear to already have access.", True) shared_sd.resolve_ticket() return if shared_ldap.add_to_group("hackbox-users", account_dn): shared_sd.post_comment( "Access has been granted. Please ensure you read " "https://collaborate.linaro.org/display/IKB/Hackbox2 " "and associated documentation so that you fully understand " "what this service is, how to use it and what the limitations " "are.", True) shared_sd.resolve_ticket() else: shared_sd.post_comment( "A problem occurred while adding you to the permission list. " "It will be necessary to get IT Services to investigate.", True) # Deassign the ticket shared_sd.assign_issue_to(None) shared_sd.transition_request_to("Waiting for Support")
def create(ticket_data): """Triggered when the issue is created.""" # Who is this ticket about? person = get_affected_person(ticket_data) person_dn = shared_ldap.find_single_object_from_email( person["emailAddress"]) if person_dn is None: # Shouldn't happen because we use a people picker shared_sd.post_comment("Unable to find this person in LDAP.", True) shared_sd.resolve_ticket(resolution_state="Declined") return # Change the ticket summary to include their name shared_sd.set_summary( "%s: %s" % (ticket_data["issue"]["fields"]["summary"], person["displayName"])) # Is this person changing team? Has a new manager been provided? cf_engineering_team = custom_fields.get("Engineering Team") cf_reports_to = custom_fields.get("Reports To") new_department = shared_sd.get_field(ticket_data, cf_engineering_team) reports_to = shared_sd.get_field(ticket_data, cf_reports_to) if new_department is not None and reports_to is None: shared_sd.post_comment( "WARNING! You are changing the department/team for this " "person but you have not provided a new manager. Please " "note that if the person will have a new manager as a " "result of changing department/team, you will need to [create a separate " "request|https://servicedesk.linaro.org/servicedesk/customer/" "portal/13/create/233] as you cannot edit the information " "provided in this ticket.", True) # If a new manager has been provided, add them as a request participant new_mgr = None if reports_to is not None: new_mgr = reports_to["emailAddress"] # ... but only if they aren't the person who raised the ticket. If # they raised the ticket, the current manager will automatically # be added (to approve the ticket). if new_mgr != shared.globals.REPORTER: shared_sd.add_request_participant(new_mgr) shared_sd.post_comment( "Adding %s for visibility of this request as the proposed " "new manager." % new_mgr, True) # Create an internal comment for HR that specifies all of the bits that # need to be done. post_hr_guidance(new_department, reports_to, new_mgr, ticket_data) # Get their manager mgr_email = shared_ldap.get_manager_from_dn(person_dn) if mgr_email is None: # Fall back to getting Diane to approve the ticket mgr_email = "*****@*****.**" shared_sd.post_comment( "Cannot find a manager for %s, defaulting to Diane." % person_dn, False) # Get their Exec exec_email = linaro_shared.get_exec_from_dn(person_dn) # This can fail if an intermediate manager is leaving Linaro, in # which case find the Exec for the proposed new manager. if exec_email is None and new_mgr is not None: new_mgr_dn = shared_ldap.find_single_object_from_email(new_mgr) exec_email = linaro_shared.get_exec_from_dn(new_mgr_dn) if exec_email is not None: cf_approvers = custom_fields.get("Executive Approvers") shared_sd.assign_approvers([exec_email], cf_approvers) else: shared_sd.post_comment("Cannot find an exec for %s" % person_dn, False) # If the ticket wasn't created by the manager, get the manager to approve # it. post_approval_message(mgr_email, exec_email, person)
def create(ticket_data): """ Create handler. """ if not shared_ldap.is_user_in_group("employees", shared.globals.REPORTER): shared_sd.post_comment( "Sorry but only Linaro employees can use this Service Request.", True) shared_sd.resolve_ticket("Declined") return cf_group_name = custom_fields.get("Group Name") cf_group_description = custom_fields.get("Group Description") cf_group_owners = custom_fields.get("Group Owners") cf_group_email_address = custom_fields.get("Group Email Address") group_display_name = shared_sd.get_field(ticket_data, cf_group_name).strip() # Take the group name, make it lower case, replace spaces with hyphens and # remove any other potentially troublesome characters. group_name = re.sub(r"\s+", '-', group_display_name.lower()) group_name = re.sub(r"[^\w\s-]", '', group_name) group_email_address = shared_sd.get_field(ticket_data, cf_group_email_address) if group_email_address is None: group_email_address = group_name + "@linaro.org" group_domain = "linaro.org" else: group_email_address = group_email_address.strip().lower() # Check we have a domain if "@" not in group_email_address: group_email_address += "@linaro.org" group_domain = "linaro.org" else: group_domain = group_email_address.split('@')[1] shared_sd.set_summary("Create LDAP group for %s" % group_email_address) result = shared_ldap.find_from_email(group_email_address) if result is not None: reply = ( "Cannot create this group because the email address ('%s') is " "already being used by the LDAP object %s" % (group_email_address, result)) shared_sd.post_comment(reply, True) shared_sd.resolve_ticket("Won't Do") return result = shared_ldap.find_from_attribute("cn", group_name) if result is not None: reply = ("Cannot create this group because the name ('%s') is already " "being used by the LDAP object %s" % (group_name, result)) shared_sd.post_comment(reply, True) shared_sd.resolve_ticket("Won't Do") return google = shared_google.check_group_alias(group_email_address) if google is not None: shared_sd.post_comment( "Cannot create this group because the email address is an alias " "for the group %s" % google, True) shared_sd.resolve_ticket("Won't Do") return group_description = shared_sd.get_field(ticket_data, cf_group_description).strip() if "=" in group_description or "=" in group_display_name: shared_sd.post_comment( "Sorry but due to a Google limitation it is not possible to " "create a group that uses the equal sign in the group's name or " "description.", True) shared_sd.resolve_ticket("Won't Do") return owner_list = [] group_owners = shared_sd.get_field(ticket_data, cf_group_owners) if group_owners is not None: owner_list = process_group_owners(group_owners) if owner_list == []: owner_list = handle_empty_owners() result = shared_ldap.create_group(group_name, group_description, group_display_name, group_email_address, owner_list) if result is not None: shared_sd.post_comment( "Sorry but something when wrong while creating the group. " "IT Services will investigate further.", True) shared_sd.post_comment(json.dumps(result), False) shared_sd.transition_request_to("Waiting for Support") return linaro_shared.trigger_google_sync() # If the user has specified a custom email address, the URL for the group # uses *that* instead of the group's name. So, basically, always use the # bit that comes before the domain string. group_name = group_email_address.split('@')[0] response = ("A group has been created on Linaro Login with an" " email address of %s.\r\n" "To change who can post to the group, or to change the " "conversation history setting, go to:\r\n" "https://groups.google.com/a/%s/g/%s/settings#posting\r\n" "\r\nIMPORTANT! Do not change group membership via Google." " It MUST be done via the [Add/Remove Users from Group|" "https://servicedesk.linaro.org/servicedesk/customer/portal/" "3/create/139] request otherwise changes will be lost.") shared_sd.post_comment( response % (group_email_address, group_domain, group_name), True) shared_sd.resolve_ticket()
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")