Ejemplo n.º 1
0
def comment(ticket_data):
    """ Triggered when a comment is posted """
    last_comment, keyword = shared_sd.central_comment_handler(
        ["add", "remove"], ["help", "retry"], False)

    if keyword == "help":
        shared_sd.post_comment(
            ("All bot commands must be internal comments and the first"
             " word/phrase in the comment.\r\n\r\n"
             "Valid commands are:\r\n"
             "* retry to ask the bot to process the request again after issues"
             " have been resolved."), False)
        return

    if keyword == "retry":
        create(ticket_data)
        return

    if (linaro_shared.ok_to_process_public_comment(last_comment) and
        (keyword is None
         or not process_public_comment(ticket_data, last_comment, keyword))):
        shared_sd.post_comment(
            "Your comment has not been recognised as an instruction to the"
            " bot so the ticket will be left for IT Services to review.", True)
        shared_sd.deassign_ticket_if_appropriate(last_comment)
Ejemplo 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()
Ejemplo n.º 3
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()
Ejemplo n.º 4
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()
Ejemplo n.º 5
0
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 clean_up_account(account):
    """Remove the account from any groups."""
    if "memberOf" in account:
        sd_comment = ""
        for grp in account["memberOf"].values:
            if shared_ldap.remove_from_group(grp, account.entry_dn):
                sd_comment += "Removed from %s\r\n" % grp
            else:
                sd_comment += "Failed to remove from %s\r\n" % grp
        if sd_comment != "":
            shared_sd.post_comment(sd_comment, True)
def check_secretary(account_dn):
    """Flag up any accounts that have this account as their Member company manager."""
    result = shared_ldap.find_matching_objects("(secretary=%s)" % account_dn,
                                               ["cn"])
    if result is not None:
        alert = (
            "[[email protected]] Need to modify the following accounts as they "
            "reference %s as their Member Company Line Manager.\r\n" %
            account_dn)
        for entry in result:
            alert += "* %s\r\n" % entry.entry_dn
        shared_sd.post_comment(alert, False)
Ejemplo n.º 8
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)
Ejemplo n.º 9
0
def comment(ticket_data):
    """ Comment handler """
    last_comment, keyword = shared_sd.central_comment_handler(
        [], ["help", "retry"])
    if keyword == "help":
        shared_sd.post_comment(
            "All bot commands must be internal comments and the first "
            "word/phrase in the comment.\r\n\r\n"
            "Valid commands are:\r\n"
            "* retry to ask the bot to process the request again after issues have been resolved.",
            False)
    elif keyword == "retry":
        create(ticket_data)
    elif last_comment is not None and last_comment['public']:
        shared_sd.deassign_ticket_if_appropriate(comment)
def transition_leaver(account_dn, email_address):
    """ Transition a leaver account back to a Member account. """
    account = shared_ldap.get_object(
        account_dn, ["passwordSelfResetBackupMail", "memberOf"])
    if "passwordSelfResetBackupMail" not in account:
        shared_sd.post_comment(
            "Cannot transition '%s' because there isn't a private email "
            "address stored in LDAP. Please provide it to IT Services." %
            email_address, True)
        return RESULT_STATE.Customer

    clean_up_account(account)
    return transition_account(account,
                              account["passwordSelfResetBackupMail"].value,
                              email_address)
Ejemplo n.º 11
0
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
Ejemplo n.º 12
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 transition_user_account(email_address):
    """ Transition the account corresponding to the email address. """
    account_dn = shared_ldap.find_from_email(email_address)
    if account_dn is None:
        shared_sd.post_comment("Cannot find '%s'" % email_address, True)
        return RESULT_STATE.Customer

    parts = account_dn.split(",", 2)
    if parts[1] == "ou=leavers":
        return transition_leaver(account_dn, email_address)
    elif parts[1] == "ou=staff":
        shared_sd.post_comment(
            "Cannot transition '%s' because this is an active Linaro account. "
            "It is only possible to transition leaving Linaro accounts." %
            email_address, True)
        return RESULT_STATE.Customer

    return transition_member(account_dn, email_address)
def transition_member(account_dn, email_address):
    """ Transition a Member account to be a Staff account. """
    account = shared_ldap.get_object(account_dn,
                                     ["givenName", "sn", "memberOf"])
    if "givenName" in account:
        new_email = "*****@*****.**" % (account["givenName"].value,
                                          account["sn"].value)
    else:
        new_email = "*****@*****.**" % account["sn"].value
    new_email = new_email.lower()
    check = shared_ldap.find_matching_objects("(mail=%s)" % new_email, ["cn"])
    if check is not None:
        shared_sd.post_comment(
            "Can't transition %s because the calculated new email address (%s) "
            "is in use already." % (email_address, new_email), True)
        shared_sd.post_comment(check[0].entry_dn, False)
        return RESULT_STATE.Customer
    # Good to go ...
    clean_up_account(account)
    return transition_account(account, email_address, new_email)
Ejemplo n.º 15
0
def handle_empty_owners():
    """
    Try to add the reporter as a fall-back owner or if no alternative
    owners have been specified. This should work since only employees
    are allowed to use this request type and all employees are in LDAP.
    """
    result = shared_ldap.find_from_email(shared.globals.REPORTER)
    if result is not None:
        shared_sd.post_comment(
            "Adding %s as the owner of the group." % shared.globals.REPORTER,
            True)
        return [result]

    # OK - something stupid is happening but let's give ourselves
    # a safety net.
    shared_sd.post_comment(
        "Unable to add %s as an owner as the email address cannot be "
        "found in Linaro Login. This means the automation has not "
        "been able to find any of the specified email addresses in "
        "Linaro Login. Consequently, IT Services will need to manage "
        "it in the interim." % shared.globals.REPORTER, True)
    return ["cn=its,ou=mailing,ou=groups,dc=linaro,dc=org"]
Ejemplo n.º 16
0
def process_group_owners(data):
    """
    We ask for owners as email addresses but LDAP needs the DN for the
    appropriate object
    """
    owner_list = []
    owners = data.split("\r\n")
    for owner in owners:
        if owner != "":
            result = shared_ldap.find_from_email(owner)
            if result is None:
                shared_sd.post_comment(
                    "Unable to add %s as an owner as the email address "
                    "cannot be found in Linaro Login." % owner, True)
            else:
                # Need to make sure we append the mailing group if it
                # is a group!
                if ",ou=security," not in result:
                    owner_list.append(result)
                    shared_sd.post_comment("Adding %s as an owner." % owner,
                                           True)
    return owner_list
Ejemplo n.º 17
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
Ejemplo n.º 18
0
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")
Ejemplo n.º 20
0
def batch_process_ownership_changes(
        group_cn, batch, auto=False, change_to_make=None):
    """Process a list of changes to the ownership."""
    # This is used for the initial ticket (which doesn't specify add/remove)
    # and for followup comments (which *does* specify add/remove). 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.
    #
    # 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 owners to sanity check the request.
    owners = get_group_owners(group_cn)
    response = ""
    keyword = determine_change_keyword(auto, change_to_make)

    for change in batch:
        if change != "":
            email_address, keyword = parse_change_line(auto, change, keyword)
            local_change, got_error, response = process_change(
                keyword, email_address, owners, group_cn, response)
            if got_error:
                break
            change_made = change_made or local_change

    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)
Ejemplo n.º 21
0
def trigger_google_sync(level=""):
    """Connect to Linaro Login over SSH to trigger GCDS."""
    pem = shared_vault.get_secret("secret/misc/it-support-bot.pem")
    stdout_data, stderr_data, status_code = ssh("login-us-east-1.linaro.org",
                                                "it-support-bot", pem, 100,
                                                level)
    if status_code == 0:
        shared_sd.post_comment(
            "Synchronisation to Google triggered. It may take up to 15 "
            "minutes before the changes are visible on Google.", True)
    else:
        shared_sd.post_comment(
            "Got non-zero status code from trigggering GCDS.", False)
        if stdout_data != "":
            shared_sd.post_comment("stdout:\r\n%s" % stdout_data, False)
        if stderr_data != "":
            shared_sd.post_comment("stderr:\r\n%s" % stderr_data, False)
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 transition_account(account, new_email, old_email):
    """Move the account and change the email address at the same time."""
    new_ou = shared_ldap.find_best_ou_for_email(new_email)
    # Make sure we're actually moving the account! Check against existing OU.
    old_ou = account.entry_dn.split(",", 1)[1]
    if old_ou == new_ou:
        shared_sd.post_comment(
            "Can't transition %s: OU is stuck at %s. IT Services needs to investigate "
            "further." % (old_email, old_ou), True)
        return RESULT_STATE.IT
    # Change the mail and cn attributes.
    shared_ldap.replace_attribute_value(account.entry_dn, "mail", new_email)
    shared_ldap.replace_attribute_value(account.entry_dn, "cn", new_email)
    # Remove various attributes if they are there ..
    for attr in [
            "userPassword", "businessCategory", "departmentNumber",
            "employeeNumber", "employeeType", "l", "labeledURI", "manager",
            "o", "passwordSelfResetBackupMail", "roomNumber", "secretary",
            "title"
    ]:
        shared_ldap.replace_attribute_value(account.entry_dn, attr, None)
    # If there are any staff accounts that have this entry as their Member Company Line
    # Manager, need to update the reference
    check_secretary(account.entry_dn)
    # Finally, rename and move the account
    result = shared_ldap.move_object(account.entry_dn, new_ou)
    if result is None:
        shared_sd.post_comment(
            "Successfully transitioned %s to %s.\r\n"
            "Please note that the account does not have a password set, nor is it in any "
            "groups." % (old_email, new_ou), True)
        return RESULT_STATE.Done

    shared_sd.post_comment(
        "Got error when trying to move %s to %s. IT Services needs to investigate "
        "further." % (old_email, new_ou), True)
    shared_sd.post_comment("The error was: %s" % result, False)
    return RESULT_STATE.IT
Ejemplo n.º 24
0
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")
Ejemplo n.º 25
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)
Ejemplo n.º 26
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")
Ejemplo n.º 27
0
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)
Ejemplo n.º 28
0
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)
Ejemplo n.º 29
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()