Esempio n. 1
0
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()
Esempio n. 2
0
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()
Esempio n. 3
0
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()
Esempio n. 4
0
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")
Esempio n. 7
0
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)
Esempio n. 8
0
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
Esempio n. 12
0
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()
Esempio n. 13
0
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'])
Esempio n. 14
0
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")