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 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 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_hr_guidance(new_department, reports_to, new_mgr, ticket_data): """ Post a private comment making it clear what HR need to do. """ comment = "" if new_department is not None: comment += ("* Change department/team to %s\r\n" % new_department["value"]) if reports_to is not None: comment += ("* Change manager to %s\r\n" % new_mgr) cf_new_job_title = custom_fields.get("New job title") new_job_title = shared_sd.get_field(ticket_data, cf_new_job_title) if new_job_title is not None: comment += ("* Change job title to %s\r\n" % new_job_title) if comment != "": shared_sd.post_comment( "HR: here is a summary of the changes to be made:\r\n%s" % comment, False)
def post_approval_message(mgr_email, exec_email, person): """ Move to next step in the approval process. """ if mgr_email not in (shared.globals.REPORTER, exec_email): shared_sd.post_comment( "As you are not the manager for %s, %s will be asked to " "approve or decline your request." % (person["displayName"], mgr_email), True) if exec_email is not None: shared_sd.post_comment( "If that approval is given, %s will then be asked to approve " "or decline your request." % exec_email, True) cf_approvers = custom_fields.get("Approvers") shared_sd.assign_approvers([mgr_email], cf_approvers) else: if exec_email is not None: shared_sd.post_comment( "%s will be asked to approve or decline your " "request." % exec_email, True) shared_sd.transition_request_to("Executive Approval")
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): """ 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): """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 get_affected_person(ticket_data): """ Return the email address for the affected person. """ cf_who_picker = custom_fields.get("Employee/Contractor") who = shared_sd.get_field(ticket_data, cf_who_picker) return who
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 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 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")