Пример #1
0
def recv_notification(configuration, path):
    """Read notification event from file"""
    logger = configuration.logger
    # logger.debug("read_notification: %s" % file)
    status = True
    new_notification = unpickle(path, logger)
    if not new_notification:
        logger.error("Failed to unpickle: %s" % path)
        return False
    user_id = new_notification.get('user_id', '')
    # logger.debug("Received user_id: '%s'" % user_id)
    if not user_id:
        status = False
        logger.error("Missing user_id in notification: %s" % path)
    else:
        client_id = expand_openid_alias(user_id, configuration)
        # logger.debug("resolved client_id: '%s'" % client_id)
        if not client_id or not extract_field(client_id, 'email'):
            status = False
            logger.error("Failed to resolve client_id from user_id: '%s'" %
                         user_id)
    if status:
        category = new_notification.get('category', [])
        # logger.debug("Received category: %s" % category)
        if not isinstance(category, list):
            status = False
            logger.error("Received category: %s must be a list" % category)
    if status:
        logger.info("Received event: %s, from: '%s'" % (category, client_id))
        new_timestamp = new_notification.get('timestamp')
        message = new_notification.get('message', '')
        # logger.debug("Received message: %s" % message)
        client_dict = received_notifications.get(client_id, {})
        if not client_dict:
            received_notifications[client_id] = client_dict
        files_list = client_dict.get('files', [])
        if not files_list:
            client_dict['files'] = files_list
        if path in files_list:
            logger.warning("Skipping previously received notification: '%s'" %
                           path)
        else:
            files_list.append(path)
            client_dict['timestamp'] = min(
                client_dict.get('timestamp', sys.maxsize), new_timestamp)
            messages_dict = client_dict.get('messages', {})
            if not messages_dict:
                client_dict['messages'] = messages_dict
            header = " ".join(category)
            if not header:
                header = '* UNKNOWN *'
            body_dict = messages_dict.get(header, {})
            if not body_dict:
                messages_dict[header] = body_dict
            message_count = body_dict.get(message, 0)
            body_dict[message] = message_count + 1

    return status
Пример #2
0
def send_notifications(configuration):
    """Generate message and send notification to users"""
    logger = configuration.logger
    # logger.debug("send_notifications")
    result = []
    for (client_id, client_dict) in received_notifications.iteritems():
        timestamp = client_dict.get('timestamp', 0)

        timestr = (
            datetime.fromtimestamp(timestamp)).strftime('%d/%m/%Y %H:%M:%S')
        client_name = extract_field(client_id, 'full_name')
        client_email = extract_field(client_id, 'email')
        recipient = "%s <%s>" % (client_name, client_email)
        total_events = 0
        notify_message = ""
        messages_dict = client_dict.get('messages', {})
        for (header, value) in messages_dict.iteritems():
            if notify_message:
                notify_message += "\n\n"
            notify_message += "= %s =\n" % header
            for (message, events) in value.iteritems():
                notify_message += "#%s : %s\n" % (events, message)
                total_events += events
        subject = "System notification: %s new events" % total_events
        notify_message = "Found %s new events since: %s\n\n" \
            % (total_events, timestr) \
            + notify_message
        status = send_email(recipient, subject, notify_message, logger,
                            configuration)
        if status:
            logger.info("Send email with %s events to: %s" %
                        (total_events, recipient))
            result.append(client_id)
        else:
            logger.error("Failed to send email to: '%s', '%s'" %
                         (recipient, client_id))

    return result
Пример #3
0
def get_twofactor_secrets(configuration, client_id):
    """Load twofactor base32 key and OTP uri for QR code. Generates secret for
    user if not already done. Actual twofactor login requirement is not
    enabled here, however.
    """
    _logger = configuration.logger
    if pyotp is None:
        raise Exception("The pyotp module is missing and required for 2FA")
    if configuration.site_enable_gdp:
        client_id = get_base_client_id(configuration,
                                       client_id,
                                       expand_oid_alias=False)
    # NOTE: 2FA secret key is a standalone file in user settings dir
    #       Try to load existing and generate new one if not there.
    #       We need the base32-encoded form as returned here.
    b32_key = load_twofactor_key(client_id, configuration)
    if not b32_key:
        b32_key = reset_twofactor_key(client_id, configuration)

    totp = get_totp(client_id, b32_key, configuration)

    # URI-format for otp auth is
    # otpauth://<otptype>/(<issuer>:)<accountnospaces>?
    #         secret=<secret>(&issuer=<issuer>)(&image=<imageuri>)
    # which we pull out of pyotp directly.
    # IMPORTANT: we use the QRious JS library to keep rendering local.
    if configuration.user_openid_alias:
        username = extract_field(client_id, configuration.user_openid_alias)
    else:
        username = client_id
    otp_uri = totp.provisioning_uri(username,
                                    issuer_name=configuration.short_title)
    # Some auth apps like FreeOTP support addition of logo with &image=PNG_URL
    # Testing is easy with https://freeotp.github.io/qrcode.html
    if configuration.site_logo_left.endswith('.png'):
        logo_url = configuration.site_logo_left
        # NOTE: image URL must a full URL and logo_url is abs or anchored path
        if not logo_url.startswith('http'):
            # Remove any leading slashes which would break join
            logo_url = os.path.join(configuration.migserver_https_sid_url,
                                    logo_url.lstrip('/'))
        # Clear 'safe' argument to also encode slashes in url
        otp_uri += '&image=%s' % urllib.quote(logo_url, '')

    # IMPORTANT: pyotp unicode breaks wsgi when inserted - force utf8!
    otp_uri = force_utf8(otp_uri)

    return (b32_key, totp.interval, otp_uri)
Пример #4
0
def main(client_id, user_arguments_dict):
    """Main function used by front end"""

    (configuration, logger, output_objects, op_name) = \
        initialize_main_variables(client_id, op_header=False)
    defaults = signature()[1]
    title_entry = find_entry(output_objects, 'title')
    label = "%s" % configuration.site_vgrid_label
    title_entry['text'] = '%s send request' % configuration.short_title
    output_objects.append({
        'object_type': 'header',
        'text': '%s send request' % configuration.short_title
    })
    (validate_status, accepted) = validate_input_and_cert(
        user_arguments_dict,
        defaults,
        output_objects,
        client_id,
        configuration,
        allow_rejects=False,
    )
    if not validate_status:
        return (accepted, returnvalues.CLIENT_ERROR)

    target_id = client_id
    vgrid_name = accepted['vgrid_name'][-1].strip()
    visible_user_names = accepted['cert_id']
    visible_res_names = accepted['unique_resource_name']
    request_type = accepted['request_type'][-1].strip().lower()
    request_text = accepted['request_text'][-1].strip()
    protocols = [proto.strip() for proto in accepted['protocol']]
    use_any = False
    if any_protocol in protocols:
        use_any = True
        protocols = configuration.notify_protocols
    protocols = [proto.lower() for proto in protocols]

    if not safe_handler(configuration, 'post', op_name, client_id,
                        get_csrf_limit(configuration), accepted):
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            '''Only accepting
CSRF-filtered POST requests to prevent unintended updates'''
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    valid_request_types = [
        'resourceowner', 'resourceaccept', 'resourcereject', 'vgridowner',
        'vgridmember', 'vgridresource', 'vgridaccept', 'vgridreject', 'plain'
    ]
    if not request_type in valid_request_types:
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            '%s is not a valid request_type (valid types: %s)!' %
            (request_type.lower(), valid_request_types)
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    if not protocols:
        output_objects.append({
            'object_type': 'error_text',
            'text': 'No protocol specified!'
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    user_map = get_user_map(configuration)
    reply_to = user_map[client_id][USERID]
    # Try to point replies to client_id email
    client_email = extract_field(reply_to, 'email')

    if request_type == "plain":
        if not visible_user_names:
            output_objects.append({
                'object_type': 'error_text',
                'text': 'No user ID specified!'
            })
            return (output_objects, returnvalues.CLIENT_ERROR)

        user_id = visible_user_names[-1].strip()
        anon_map = anon_to_real_user_map(configuration)
        if user_id in anon_map:
            user_id = anon_map[user_id]
        if user_id not in user_map:
            output_objects.append({
                'object_type': 'error_text',
                'text': 'No such user: %s' % user_id
            })
            return (output_objects, returnvalues.CLIENT_ERROR)
        target_name = user_id
        user_dict = user_map[user_id]
        vgrid_access = user_vgrid_access(configuration, client_id)
        vgrids_allow_email = user_dict[CONF].get('VGRIDS_ALLOW_EMAIL', [])
        vgrids_allow_im = user_dict[CONF].get('VGRIDS_ALLOW_IM', [])
        if any_vgrid in vgrids_allow_email:
            email_vgrids = vgrid_access
        else:
            email_vgrids = set(vgrids_allow_email).intersection(vgrid_access)
        if any_vgrid in vgrids_allow_im:
            im_vgrids = vgrid_access
        else:
            im_vgrids = set(vgrids_allow_im).intersection(vgrid_access)
        if use_any:
            # Do not try disabled protocols if ANY was requested
            if not email_vgrids:
                protocols = [
                    proto for proto in protocols
                    if proto not in email_keyword_list
                ]
            if not im_vgrids:
                protocols = [
                    proto for proto in protocols if proto in email_keyword_list
                ]
        if not email_vgrids and [
                proto for proto in protocols if proto in email_keyword_list
        ]:
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'You are not allowed to send emails to %s!' % user_id
            })
            return (output_objects, returnvalues.CLIENT_ERROR)
        if not im_vgrids and [
                proto for proto in protocols if proto not in email_keyword_list
        ]:
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'You are not allowed to send instant messages to %s!' % user_id
            })
            return (output_objects, returnvalues.CLIENT_ERROR)
        for proto in protocols:
            if not user_dict[CONF].get(proto.upper(), False):
                if use_any:
                    # Remove missing protocols if ANY protocol was requested
                    protocols = [i for i in protocols if i != proto]
                else:
                    output_objects.append({
                        'object_type':
                        'error_text',
                        'text':
                        'User %s does not accept %s messages!' %
                        (user_id, proto)
                    })
                    return (output_objects, returnvalues.CLIENT_ERROR)
        if not protocols:
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'User %s does not accept requested protocol(s) messages!' %
                user_id
            })
            return (output_objects, returnvalues.CLIENT_ERROR)
        target_list = [user_id]
    elif request_type in ["vgridaccept", "vgridreject"]:
        # Always allow accept messages but only between owners/members
        if not visible_user_names and not visible_res_names:
            output_objects.append({
                'object_type': 'error_text',
                'text': 'No user or resource ID specified!'
            })
            return (output_objects, returnvalues.CLIENT_ERROR)
        if not vgrid_name:
            output_objects.append({
                'object_type': 'error_text',
                'text': 'No vgrid_name specified!'
            })
            return (output_objects, returnvalues.CLIENT_ERROR)
        if vgrid_name.upper() == default_vgrid.upper():
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'No requests for %s are allowed!' % default_vgrid
            })
            return (output_objects, returnvalues.CLIENT_ERROR)
        if not vgrid_is_owner(vgrid_name, client_id, configuration):
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'You are not an owner of %s or a parent %s!' %
                (vgrid_name, label)
            })
            return (output_objects, returnvalues.CLIENT_ERROR)
        # NOTE: we support exactly one vgrid but multiple users/resources here
        if visible_user_names:
            logger.info("setting user recipients: %s" % visible_user_names)
            target_list = [user_id.strip() for user_id in visible_user_names]
        elif visible_res_names:
            # vgrid resource accept - lookup and notify resource owners
            logger.info("setting res owner recipients: %s" % visible_res_names)
            target_list = []
            for unique_resource_name in visible_res_names:
                logger.info("loading res owners for %s" % unique_resource_name)
                (load_status,
                 res_owners) = resource_owners(configuration,
                                               unique_resource_name)
                if not load_status:
                    output_objects.append({
                        'object_type':
                        'error_text',
                        'text':
                        'Could not lookup owners of %s!' % unique_resource_name
                    })
                    continue
                logger.info("adding res owners to recipients: %s" % res_owners)
                target_list += [user_id for user_id in res_owners]

        target_id = '%s %s owners' % (vgrid_name, label)
        target_name = vgrid_name
    elif request_type in ["resourceaccept", "resourcereject"]:
        # Always allow accept messages between actual resource owners
        if not visible_user_names:
            output_objects.append({
                'object_type': 'error_text',
                'text': 'No user ID specified!'
            })
            return (output_objects, returnvalues.CLIENT_ERROR)
        if not visible_res_names:
            output_objects.append({
                'object_type': 'error_text',
                'text': 'No resource ID specified!'
            })
            return (output_objects, returnvalues.CLIENT_ERROR)
        # NOTE: we support exactly one resource but multiple users here
        unique_resource_name = visible_res_names[-1].strip()
        target_name = unique_resource_name
        res_map = get_resource_map(configuration)
        if unique_resource_name not in res_map:
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'No such resource: %s' % unique_resource_name
            })
            return (output_objects, returnvalues.CLIENT_ERROR)
        owners_list = res_map[unique_resource_name][OWNERS]
        if not client_id in owners_list:
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'You are not an owner of %s!' % unique_resource_name
            })
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'Invalid resource %s message!' % request_type
            })
            return (output_objects, returnvalues.CLIENT_ERROR)
        target_id = '%s resource owners' % unique_resource_name
        target_name = unique_resource_name
        target_list = [user_id.strip() for user_id in visible_user_names]
    elif request_type == "resourceowner":
        if not visible_res_names:
            output_objects.append({
                'object_type': 'error_text',
                'text': 'No resource ID specified!'
            })
            return (output_objects, returnvalues.CLIENT_ERROR)
        # NOTE: we support exactly one resource but multiple users here
        unique_resource_name = visible_res_names[-1].strip()
        anon_map = anon_to_real_res_map(configuration.resource_home)
        if unique_resource_name in anon_map:
            unique_resource_name = anon_map[unique_resource_name]
        target_name = unique_resource_name
        res_map = get_resource_map(configuration)
        if unique_resource_name not in res_map:
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'No such resource: %s' % unique_resource_name
            })
            return (output_objects, returnvalues.CLIENT_ERROR)
        target_list = res_map[unique_resource_name][OWNERS]
        if client_id in target_list:
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'You are already an owner of %s!' % unique_resource_name
            })
            return (output_objects, returnvalues.CLIENT_ERROR)

        request_dir = os.path.join(configuration.resource_home,
                                   unique_resource_name)
        access_request = {
            'request_type': request_type,
            'entity': client_id,
            'target': unique_resource_name,
            'request_text': request_text
        }
        if not save_access_request(configuration, request_dir, access_request):
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'Could not save request - owners may still manually add you'
            })
            return (output_objects, returnvalues.SYSTEM_ERROR)
    elif request_type in ["vgridmember", "vgridowner", "vgridresource"]:
        if not vgrid_name:
            output_objects.append({
                'object_type': 'error_text',
                'text': 'No vgrid_name specified!'
            })
            return (output_objects, returnvalues.CLIENT_ERROR)

        # default vgrid is read-only

        if vgrid_name.upper() == default_vgrid.upper():
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'No requests for %s are not allowed!' % default_vgrid
            })
            return (output_objects, returnvalues.CLIENT_ERROR)

        # stop owner or member request if already an owner
        # and prevent repeated resource access requests

        if request_type == 'vgridresource':
            # NOTE: we support exactly one resource here
            unique_resource_name = visible_res_names[-1].strip()
            target_id = entity = unique_resource_name
            if vgrid_is_resource(vgrid_name, unique_resource_name,
                                 configuration):
                output_objects.append({
                    'object_type':
                    'error_text',
                    'text':
                    'You already have access to %s or a parent %s.' %
                    (vgrid_name, label)
                })
                return (output_objects, returnvalues.CLIENT_ERROR)
        else:
            target_id = entity = client_id
            if vgrid_is_owner(vgrid_name, client_id, configuration):
                output_objects.append({
                    'object_type':
                    'error_text',
                    'text':
                    'You are already an owner of %s or a parent %s!' %
                    (vgrid_name, label)
                })
                return (output_objects, returnvalues.CLIENT_ERROR)

        # only ownership requests are allowed for existing members

        if request_type == 'vgridmember':
            if vgrid_is_member(vgrid_name, client_id, configuration):
                output_objects.append({
                    'object_type':
                    'error_text',
                    'text':
                    'You are already a member of %s or a parent %s.' %
                    (vgrid_name, label)
                })
                return (output_objects, returnvalues.CLIENT_ERROR)

        # Find all VGrid owners configured to receive notifications

        target_name = vgrid_name
        (settings_status, settings_dict) = vgrid_settings(vgrid_name,
                                                          configuration,
                                                          recursive=True,
                                                          as_dict=True)
        if not settings_status:
            settings_dict = {}
        request_recipients = settings_dict.get('request_recipients',
                                               default_vgrid_settings_limit)
        # We load and use direct owners first if any - otherwise inherited
        owners_list = []
        for inherited in (False, True):
            (owners_status, owners_list) = vgrid_owners(vgrid_name,
                                                        configuration,
                                                        recursive=inherited)
            if not owners_status:
                output_objects.append({
                    'object_type':
                    'error_text',
                    'text':
                    'Failed to lookup owners for %s %s - sure it exists?' %
                    (vgrid_name, label)
                })
                return (output_objects, returnvalues.CLIENT_ERROR)
            elif owners_list:
                break
        if not owners_list:
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'Failed to lookup owners for %s %s - sure it exists?' %
                (vgrid_name, label)
            })
            return (output_objects, returnvalues.CLIENT_ERROR)
        # Now we have direct or inherited owners to notify
        target_list = owners_list[:request_recipients]

        request_dir = os.path.join(configuration.vgrid_home, vgrid_name)
        access_request = {
            'request_type': request_type,
            'entity': entity,
            'target': vgrid_name,
            'request_text': request_text
        }
        if not save_access_request(configuration, request_dir, access_request):
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'Could not save request - owners may still manually add you'
            })
            return (output_objects, returnvalues.SYSTEM_ERROR)

    else:
        output_objects.append({
            'object_type': 'error_text',
            'text': 'Invalid request type: %s' % request_type
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    # Now send request to all targets in turn
    # TODO: inform requestor if no owners have mail/IM set in their settings

    logger.debug("sending notification to recipients: %s" % target_list)

    for target in target_list:

        if not target:
            logger.warning("skipping empty notify target: %s" % target_list)
            continue

        # USER_CERT entry is destination

        notify = []
        for proto in protocols:
            notify.append('%s: SETTINGS' % proto)
        job_dict = {
            'NOTIFY': notify,
            'JOB_ID': 'NOJOBID',
            'USER_CERT': target,
            'EMAIL_SENDER': client_email
        }

        notifier = notify_user_thread(
            job_dict,
            [target_id, target_name, request_type, request_text, reply_to],
            'SENDREQUEST',
            logger,
            '',
            configuration,
        )

        # Try finishing delivery but do not block forever on one message
        notifier.join(30)
    output_objects.append({
        'object_type':
        'text',
        'text':
        'Sent %s message to %d people' % (request_type, len(target_list))
    })
    im_notify_protocols = [
        i for i in configuration.notify_protocols if i != 'email'
    ]
    if im_notify_protocols:
        enabled_notify = 'IM / email'
    else:
        enabled_notify = 'email'
    output_objects.append({
        'object_type':
        'text',
        'text':
        """Please make sure you have %s notifications
configured on your Setings page if you expect a reply to this message""" %
        enabled_notify
    })

    return (output_objects, returnvalues.OK)
Пример #5
0
def main(client_id, user_arguments_dict):
    """Main function used by front end"""

    (configuration, logger, output_objects, op_name) = \
        initialize_main_variables(client_id, op_header=False, op_menu=False)
    defaults = signature()[1]
    client_dir = client_id_dir(client_id)
    logger.debug('in peersaction: %s' % user_arguments_dict)
    (validate_status, accepted) = validate_input(user_arguments_dict,
                                                 defaults,
                                                 output_objects,
                                                 allow_rejects=False)
    if not validate_status:
        return (accepted, returnvalues.CLIENT_ERROR)

    if not safe_handler(configuration, 'post', op_name, client_id,
                        get_csrf_limit(configuration), accepted):
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            '''Only accepting
CSRF-filtered POST requests to prevent unintended updates'''
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    title_entry = find_entry(output_objects, 'title')
    title_entry['text'] = 'Save Peers'
    output_objects.append({'object_type': 'header', 'text': 'Save Peers'})

    admin_email = configuration.admin_email
    smtp_server = configuration.smtp_server
    user_pending = os.path.abspath(configuration.user_pending)

    user_map = get_full_user_map(configuration)
    user_dict = user_map.get(client_id, None)
    # Optional site-wide limitation of peers permission
    if not user_dict or \
            not peers_permit_allowed(configuration, user_dict):
        logger.warning("user %s is not allowed to permit peers!" % client_id)
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            'Only privileged users can permit external peers!'
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    action = accepted['action'][-1].strip()
    label = accepted['peers_label'][-1].strip()
    kind = accepted['peers_kind'][-1].strip()
    raw_expire = accepted['peers_expire'][-1].strip()
    peers_content = accepted['peers_content']
    peers_format = accepted['peers_format'][-1].strip()
    peers_invite = accepted['peers_invite'][-1].strip()
    do_invite = (peers_invite.lower() in ['on', 'true', 'yes', '1'])

    try:
        expire = datetime.datetime.strptime(raw_expire, '%Y-%m-%d')
        if datetime.datetime.now() > expire:
            raise ValueError("specified expire value is in the past!")
    except Exception as exc:
        logger.error("expire %r could not be parsed into a (future) date" %
                     raw_expire)
        output_objects.append({
            'object_type':
            'text',
            'text':
            'No valid expire provided - using default: %d days' %
            default_expire_days
        })
        expire = datetime.datetime.now()
        expire += datetime.timedelta(days=default_expire_days)
    expire = expire.date().isoformat()

    if not action in peer_actions:
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            'Unsupported peer action %r - only %s are allowed' %
            (action, ', '.join(peer_actions))
        })
        return (output_objects, returnvalues.CLIENT_ERROR)
    if not kind in peer_kinds:
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            'Unsupported peer kind %r - only %s are allowed' %
            (kind, ', '.join(peer_kinds))
        })
        return (output_objects, returnvalues.CLIENT_ERROR)
    # TODO: implement and enable more formats?
    if peers_format not in ("csvform", 'userid'):
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            'Only Import Peers is implemented so far!'
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    peers_path = os.path.join(configuration.user_settings, client_dir,
                              peers_filename)
    try:
        all_peers = load(peers_path)
    except Exception as exc:
        logger.warning("could not load peers from: %s" % exc)
        all_peers = {}

    # Extract peer(s) from request
    (peers, err) = parse_peers(configuration, peers_content, peers_format)
    if not err and not peers:
        err = ["No valid peers provided"]
    if err:
        output_objects.append({
            'object_type': 'error_text',
            'text': 'Parsing failed: %s' % '.\n '.join(err)
        })
        output_objects.append({
            'object_type': 'link',
            'destination': 'peers.py',
            'text': 'Back to peers'
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    # NOTE: general cases of operation here:
    # * import multiple peers in one go (add new, update existing)
    # * add one or more new peers
    # * update one or more existing peers
    # * remove one or more existing peers
    # * accept one or more pending requests
    # * reject one or more pending requests
    # The kind and expire values are generally applied for all included peers.

    # NOTE: we check all peers before any action
    for user in peers:
        fill_distinguished_name(user)
        peer_id = user['distinguished_name']
        cur_peer = all_peers.get(peer_id, {})
        if 'add' == action and cur_peer:
            output_objects.append({
                'object_type': 'error_text',
                'text': 'Peer %r already exists!' % peer_id
            })
            return (output_objects, returnvalues.CLIENT_ERROR)
        elif 'update' == action and not cur_peer:
            output_objects.append({
                'object_type': 'error_text',
                'text': 'Peer %r does not exists!' % peer_id
            })
            return (output_objects, returnvalues.CLIENT_ERROR)
        elif 'remove' == action and not cur_peer:
            output_objects.append({
                'object_type': 'error_text',
                'text': 'Peer %r does not exists!' % peer_id
            })
            return (output_objects, returnvalues.CLIENT_ERROR)
        elif 'accept' == action and cur_peer:
            output_objects.append({
                'object_type': 'error_text',
                'text': 'Peer %r already accepted!' % peer_id
            })
            return (output_objects, returnvalues.CLIENT_ERROR)
        elif 'reject' == action and cur_peer:
            output_objects.append({
                'object_type': 'error_text',
                'text': 'Peer %r already accepted!' % peer_id
            })
            return (output_objects, returnvalues.CLIENT_ERROR)
        elif 'import' == action and cur_peer:
            # Only warn on import with existing match
            output_objects.append({
                'object_type': 'text',
                'text': 'Updating existing peer %r' % peer_id
            })

    # Now apply changes
    for user in peers:
        peer_id = user['distinguished_name']
        cur_peer = all_peers.get(peer_id, {})
        user.update({'label': label, 'kind': kind, 'expire': expire})
        if 'add' == action:
            all_peers[peer_id] = user
        elif 'update' == action:
            all_peers[peer_id] = user
        elif 'remove' == action:
            del all_peers[peer_id]
        elif 'accept' == action:
            all_peers[peer_id] = user
        elif 'reject' == action:
            pass
        elif 'import' == action:
            all_peers[peer_id] = user
        logger.info("%s peer %s" % (action, peer_id))

    try:
        dump(all_peers, peers_path)
        logger.debug('%s %s peers %s in %s' %
                     (client_id, action, all_peers, peers_path))
        output_objects.append({
            'object_type': 'text',
            'text': "Completed %s peers" % action
        })
        for user in peers:
            output_objects.append({
                'object_type': 'text',
                'text': "%(distinguished_name)s" % user
            })
        if action in ['import', 'add', 'update']:
            client_name = extract_field(client_id, 'full_name')
            client_email = extract_field(client_id, 'email')

            if do_invite:
                succeeded, failed = [], []
                email_header = '%s Invitation' % configuration.short_title
                email_msg_template = """Hi %%s,
This is an automatic email sent on behalf of %s who vouched for you to get a
user account on %s. You can accept the invitation by going to
%%s
entering a password of your choice and submitting the form.
If you do not want a user account you can safely ignore this email.

We would be grateful if you report any abuse of the invitation system to the
site administrators (%s).
""" % (client_name, configuration.short_title, admin_email)
                for peer_user in peers:
                    peer_name = peer_user['full_name']
                    peer_email = peer_user['email']
                    peer_url = os.path.join(
                        configuration.migserver_https_sid_url, 'cgi-sid',
                        'reqoid.py')
                    peer_req = {}
                    for field in peers_fields:
                        peer_req[field] = peer_user.get(field, '')
                    peer_req['comment'] = 'Invited by %s (%s) for %s purposes' \
                                          % (client_name, client_email, kind)
                    # Mark ID fields as readonly in the form to limit errors
                    peer_req['ro_fields'] = keyword_auto
                    peer_url += '?%s' % urllib.urlencode(peer_req)
                    email_msg = email_msg_template % (peer_name, peer_url)
                    logger.info(
                        'Sending invitation: to: %s, header: %s, msg: %s, smtp_server: %s'
                        % (peer_email, email_header, email_msg, smtp_server))
                    if send_email(peer_email, email_header, email_msg, logger,
                                  configuration):
                        succeeded.append(peer_email)
                    else:
                        failed.append(peer_email)

                if failed:
                    output_objects.append({
                        'object_type':
                        'error_text',
                        'text':
                        """An error occured trying to email the peer
invitation to %s . Please inform the site admins (%s) if the problem persists.
""" % (', '.join(failed), admin_email)
                    })
                if succeeded:
                    output_objects.append({
                        'object_type':
                        'text',
                        'text':
                        """Sent invitation to %s with a link to a mostly pre-filled %s account request
form with the exact ID fields you provided here.""" %
                        (', '.join(succeeded), configuration.short_title)
                    })
            else:
                output_objects.append({
                    'object_type':
                    'text',
                    'text':
                    """Please tell your peers
to request an account at %s with the exact ID fields you provided here and
importantly mentioning the purpose and your email (%s) in the sign up Comment
field. Alternatively you can use the invite button to send out an email with a
link to a mostly prefilled request form.""" %
                    (configuration.short_title, client_email)
                })
    except Exception as exc:
        logger.error('Failed to save %s peers to %s: %s' %
                     (client_id, peers_path, exc))
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            '''
Could not %s peers %r. Please contact the site admins on %s if this error
persists.
''' % (action, label, admin_email)
        })
        return (output_objects, returnvalues.SYSTEM_ERROR)

    if action in ["accept", "reject"]:
        changed = [(i['distinguished_name'], i) for i in peers]
        if not manage_pending_peers(configuration, client_id, "remove",
                                    changed):
            logger.warning('could not update pending peers for %s after %s' %
                           (client_id, action))

    logger.info('%s completed for %s peers for %s in %s' %
                (action, label, client_id, peers_path))

    user_lines = []
    pretty_peers = {'label': label, 'kind': kind, 'expire': expire}
    for user in peers:
        user_lines.append(user['distinguished_name'])
    pretty_peers['user_lines'] = '\n'.join(user_lines)
    email_header = '%s Peers %s' % (configuration.short_title, action)
    email_msg = """Received %s peers from %s
""" % (action, client_id)
    email_msg += """
Kind: %(kind)s , Expire: %(expire)s, Label: %(label)s , Peers:
%(user_lines)s
""" % pretty_peers

    logger.info('Sending email: to: %s, header: %s, msg: %s, smtp_server: %s' %
                (admin_email, email_header, email_msg, smtp_server))
    if not send_email(admin_email, email_header, email_msg, logger,
                      configuration):
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            '''
An error occured trying to send the email about your %s peers to the site
administrators. Please manually inform them (%s) if the problem persists.
''' % (action, admin_email)
        })
        return (output_objects, returnvalues.SYSTEM_ERROR)

    output_objects.append({
        'object_type':
        'text',
        'text':
        '''
Informed the site admins about your %s peers action to let them accept peer
account requests you already validated.''' % action
    })

    output_objects.append({
        'object_type': 'link',
        'destination': 'peers.py',
        'text': 'Back to peers'
    })
    return (output_objects, returnvalues.OK)
Пример #6
0
def main(client_id, user_arguments_dict):
    """Main function used by front end"""
    (configuration, logger, output_objects, op_name) = \
        initialize_main_variables(client_id, op_header=False)
    client_dir = client_id_dir(client_id)
    defaults = signature()[1]
    (validate_status, accepted) = validate_input_and_cert(
        user_arguments_dict,
        defaults,
        output_objects,
        client_id,
        configuration,
        allow_rejects=False,
    )

    if not validate_status:
        return (accepted, returnvalues.CLIENT_ERROR)

    logger.debug("User: %s executing %s" % (client_id, op_name))
    if not configuration.site_enable_cloud:
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            'The cloud service is not enabled on the system'
        })
        return (output_objects, returnvalues.SYSTEM_ERROR)

    output_objects.append({
        'object_type': 'header',
        'text': 'Cloud Instance Management'
    })

    user_map = get_full_user_map(configuration)
    user_dict = user_map.get(client_id, None)
    # Optional limitation of cload access vgrid permission
    if not user_dict or not cloud_access_allowed(configuration, user_dict):
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            "You don't have permission to access the cloud facilities on "
            "this site"
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    return_status = returnvalues.OK
    action = accepted['action'][-1]
    # NOTE: instance_X may be empty list - fall back to empty string
    instance_id = ([''] + accepted['instance_id'])[-1]
    instance_label = ([''] + accepted['instance_label'])[-1]
    instance_image = ([''] + accepted['instance_image'])[-1]
    accept_terms = (([''] + accepted['accept_terms'])[-1] in ('yes', 'on'))
    cloud_id = accepted['service'][-1]
    service = {
        k: v
        for options in configuration.cloud_services
        for k, v in options.items() if options['service_name'] == cloud_id
    }

    if not service:
        valid_services = [
            options['service_name'] for options in configuration.cloud_services
        ]
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            '%s is not among the valid cloud services: %s' %
            (cloud_id, ', '.join(valid_services))
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    valid_service = valid_cloud_service(configuration, service)
    if not valid_service:
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            'The service %s appears to be misconfigured, '
            'please contact a system administrator about this issue' % cloud_id
        })
        return (output_objects, returnvalues.SYSTEM_ERROR)

    service_title = service['service_title']
    if not action in valid_actions:
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            '%s is not a valid action '
            'allowed actions include %s' % (action, ', '.join(valid_actions))
        })
        return (output_objects, returnvalues.CLIENT_ERROR)
    elif action in cloud_edit_actions:
        if not safe_handler(configuration, 'post', op_name, client_id,
                            get_csrf_limit(configuration), accepted):
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                '''Only accepting
                CSRF-filtered POST requests to prevent unintended updates'''
            })
            return (output_objects, returnvalues.CLIENT_ERROR)

    cloud_flavor = service.get("service_flavor", "openstack")
    user_home_dir = os.path.join(configuration.user_home, client_dir)

    client_email = extract_field(client_id, 'email')
    if not client_email:
        logger.error("could not extract client email for %s!" % client_id)
        output_objects.append({
            'object_type': 'error_text',
            'text': "No client ID found - can't continue"
        })
        return (output_objects, returnvalues.SYSTEM_ERROR)

    ssh_auth_msg = "Login requires your private key for your public key:"
    instance_missing_msg = "Found no '%s' instance at %s. Please contact a " \
                           + "site administrator if it should be there."

    _label = instance_label
    if instance_id and not _label:
        _, _label, _ = cloud_split_instance_id(configuration, client_id,
                                               instance_id)

    if "create" == action:
        if not accept_terms:
            logger.error("refusing create without accepting terms for %s!" %
                         client_id)
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                "You MUST accept the cloud user terms to create instances"
            })
            return (output_objects, returnvalues.CLIENT_ERROR)

        # Load all instances and make sure none contains label in ID
        saved_instances = cloud_load_instance(configuration, client_id,
                                              cloud_id, keyword_all)
        for (saved_id, instance) in saved_instances.items():
            if instance_label == instance.get('INSTANCE_LABEL', saved_id):
                logger.error("Refused %s re-create %s cloud instance %s!" %
                             (client_id, cloud_id, instance_label))
                output_objects.append({
                    'object_type':
                    'error_text',
                    'text':
                    "You already have an instance with the label '%s'!" %
                    instance_label
                })
                return (output_objects, returnvalues.CLIENT_ERROR)

        max_instances = lookup_user_service_value(
            configuration, client_id, service, 'service_max_user_instances')
        max_user_instances = int(max_instances)
        # NOTE: a negative max value means unlimited but 0 or more is enforced
        if max_user_instances >= 0 and \
                len(saved_instances) >= max_user_instances:
            logger.error("Refused %s create additional %s cloud instances!" %
                         (client_id, cloud_id))
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                "You already have the maximum allowed %s instances (%d)!" %
                (service_title, max_user_instances)
            })
            return (output_objects, returnvalues.CLIENT_ERROR)

        if not instance_label:
            logger.error("Refused %s create unlabelled %s cloud instance!" %
                         (client_id, cloud_id))
            output_objects.append({
                'object_type': 'error_text',
                'text': "No instance label provided!"
            })
            return (output_objects, returnvalues.CLIENT_ERROR)

        # Lookup user-specific allowed images (colon-separated image names)
        allowed_images = allowed_cloud_images(configuration, client_id,
                                              cloud_id, cloud_flavor)
        if not allowed_images:
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                "No valid / allowed cloud images found!"
            })
            return (output_objects, returnvalues.CLIENT_ERROR)

        if not instance_image:
            instance_image = allowed_images[0]
            logger.info("No image specified - using first for %s in %s: %s" %
                        (client_id, cloud_id, instance_image))

        image_id = None
        for (img_name, img_id, img_alias) in allowed_images:
            if instance_image == img_name:
                image_id = img_id
                break

        if not image_id:
            logger.error("No matching image ID found for %s in %s: %s" %
                         (client_id, cloud_id, instance_image))
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                "No such  image found: %s" % instance_image
            })
            return (output_objects, returnvalues.CLIENT_ERROR)

        # TODO: remove this direct key injection if we can delay it
        cloud_settings = load_cloud(client_id, configuration)
        raw_keys = cloud_settings.get('authkeys', '').split('\n')
        auth_keys = [i.split('#', 1)[0].strip() for i in raw_keys]
        auth_keys = [i for i in auth_keys if i]
        if not auth_keys:
            logger.error("No cloud pub keys setup for %s - refuse create" %
                         client_id)
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                """
You haven't provided any valid ssh pub key(s) for cloud instance login, which
is stricly required for all use. Please do so before you try again.
            """
            })
            output_objects.append({
                'object_type': 'link',
                'destination': 'setup.py?topic=cloud',
                'text': 'Open cloud setup',
                'class': 'cloudsetuplink iconspace',
                'title': 'open cloud setup',
                'target': '_blank'
            })
            return (output_objects, returnvalues.CLIENT_ERROR)

        logger.debug("Continue create for %s with auth_keys: %s" %
                     (client_id, auth_keys))

        # Create a new internal keyset and session id
        (priv_key, pub_key) = generate_ssh_rsa_key_pair(encode_utf8=True)
        session_id = generate_random_ascii(session_id_bytes,
                                           charset='0123456789abcdef')
        # We make sure to create instance with a globally unique ID on the
        # cloud while only showing the requested instance_label to the user.
        instance_id = cloud_build_instance_id(configuration, client_email,
                                              instance_label, session_id)
        # TODO: make more fields flexible/conf
        cloud_dict = {
            'INSTANCE_ID': instance_id,
            'INSTANCE_LABEL': instance_label,
            'INSTANCE_IMAGE': instance_image,
            'IMAGE_ID': image_id,
            'AUTH_KEYS': auth_keys,
            'USER_CERT': client_id,
            'INSTANCE_PRIVATE_KEY': priv_key,
            'INSTANCE_PUBLIC_KEY': pub_key,
            # don't need fraction precision, also not all systems provide fraction
            # precision.
            'CREATED_TIMESTAMP': int(time.time()),
            # Init unset ssh address and leave for floating IP assigment below
            'INSTANCE_SSH_IP': '',
            'INSTANCE_SSH_PORT': 22,
        }
        (action_status,
         action_msg) = create_cloud_instance(configuration, client_id,
                                             cloud_id, cloud_flavor,
                                             instance_id, image_id, auth_keys)
        if not action_status:
            logger.error(
                "%s %s cloud instance %s for %s failed: %s" %
                (action, cloud_id, instance_id, client_id, action_msg))
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'Your %s instance %s at %s did not succeed: %s' %
                (action, instance_label, service_title, action_msg)
            })
            return (output_objects, returnvalues.SYSTEM_ERROR)

        # On success the action_msg contains the assigned floating IP address
        instance_ssh_fqdn = action_msg
        cloud_dict['INSTANCE_SSH_IP'] = instance_ssh_fqdn
        if not cloud_save_instance(configuration, client_id, cloud_id,
                                   instance_id, cloud_dict):
            logger.error("save new %s cloud instance %s for %s failed" %
                         (cloud_id, instance_id, client_id))
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'Error saving your %s cloud instance setup' % service_title
            })
            return (output_objects, returnvalues.SYSTEM_ERROR)

        output_objects.append({
            'object_type':
            'text',
            'text':
            "%s instance %s at %s: %s" %
            (action, instance_label, service_title, "success")
        })
        output_objects.append({
            'object_type':
            'html_form',
            'text':
            _ssh_help(configuration, client_id, cloud_id, cloud_dict,
                      instance_id)
        })

    elif "delete" == action:
        saved_instance = cloud_load_instance(configuration, client_id,
                                             cloud_id, instance_id)
        if not saved_instance:
            logger.error("no saved %s cloud instance %s for %s to delete" %
                         (cloud_id, instance_id, client_id))
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                instance_missing_msg % (_label, service_title)
            })
            return (output_objects, returnvalues.CLIENT_ERROR)

        (action_status,
         action_msg) = delete_cloud_instance(configuration, client_id,
                                             cloud_id, cloud_flavor,
                                             instance_id)
        if not action_status:
            logger.error(
                "%s %s cloud instance %s for %s failed: %s" %
                (action, cloud_id, instance_id, client_id, action_msg))
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'Your %s instance %s at %s did not succeed: %s' %
                (action, _label, service_title, action_msg)
            })
            return (output_objects, returnvalues.SYSTEM_ERROR)

        if not cloud_purge_instance(configuration, client_id, cloud_id,
                                    instance_id):
            logger.error("purge %s cloud instance %s for %s failed" %
                         (cloud_id, instance_id, client_id))
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'Error deleting your %s cloud instance setup' % service_title
            })
            return (output_objects, returnvalues.SYSTEM_ERROR)

        output_objects.append({
            'object_type':
            'text',
            'text':
            "%s instance %s at %s: %s" %
            (action, _label, service_title, "success")
        })

    elif "status" == action:
        saved_instance = cloud_load_instance(configuration, client_id,
                                             cloud_id, instance_id)
        if not saved_instance:
            logger.error("no saved %s cloud instance %s for %s to query" %
                         (cloud_id, instance_id, client_id))
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                instance_missing_msg % (_label, service_title)
            })
            return (output_objects, returnvalues.CLIENT_ERROR)

        (action_status,
         action_msg) = status_of_cloud_instance(configuration, client_id,
                                                cloud_id, cloud_flavor,
                                                instance_id)
        if not action_status:
            logger.error(
                "%s %s cloud instance %s for %s failed: %s" %
                (action, cloud_id, instance_id, client_id, action_msg))
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'Your %s instance %s at %s did not succeed: %s' %
                (action, _label, service_title, action_msg)
            })
            return (output_objects, returnvalues.SYSTEM_ERROR)

        output_objects.append({
            'object_type':
            'text',
            'text':
            "%s instance %s at %s: %s" %
            (action, _label, service_title, action_msg)
        })

        # Show instance access details if running
        if action_msg in ('ACTIVE', 'RUNNING'):
            # Only include web console if explicitly configured
            if configuration.user_cloud_console_access:
                (console_status, console_msg) = web_access_cloud_instance(
                    configuration, client_id, cloud_id, cloud_flavor,
                    instance_id)
                if not console_status:
                    logger.error(
                        "%s cloud instance %s console for %s failed: %s" % \
                        (cloud_id, instance_id, client_id, console_msg))
                    output_objects.append({
                        'object_type':
                        'error_text',
                        'text':
                        'Failed to get instance %s at %s console: %s' %
                        (_label, service_title, console_msg)
                    })
                    return (output_objects, returnvalues.SYSTEM_ERROR)
                logger.info("%s cloud instance %s console for %s: %s" %
                            (cloud_id, instance_id, client_id, console_msg))
                output_objects.append({
                    'object_type': 'link',
                    'destination': console_msg,
                    'text': 'Open web console',
                    'class': 'consolelink iconspace',
                    'title': 'open web console',
                    'target': '_blank'
                })
                output_objects.append({'object_type': 'text', 'text': ''})

            output_objects.append({
                'object_type':
                'html_form',
                'text':
                _ssh_help(configuration, client_id, cloud_id, saved_instance,
                          instance_id)
            })

        output_objects.append({'object_type': 'text', 'text': ''})

    elif "start" == action:
        saved_instance = cloud_load_instance(configuration, client_id,
                                             cloud_id, instance_id)
        if not saved_instance:
            logger.error("no saved %s cloud instance %s for %s to start" %
                         (cloud_id, instance_id, client_id))
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                instance_missing_msg % (_label, service_title)
            })
            return (output_objects, returnvalues.CLIENT_ERROR)

        (action_status,
         action_msg) = start_cloud_instance(configuration, client_id, cloud_id,
                                            cloud_flavor, instance_id)
        if not action_status:
            logger.error(
                "%s %s cloud instance %s for %s failed: %s" %
                (action, cloud_id, instance_id, client_id, action_msg))
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'Your %s instance %s at %s did not succeed: %s' %
                (action, _label, service_title, action_msg)
            })
            return (output_objects, returnvalues.SYSTEM_ERROR)

        output_objects.append({
            'object_type':
            'text',
            'text':
            "%s instance %s at %s: %s" %
            (action, _label, service_title, "success")
        })
        output_objects.append({
            'object_type':
            'html_form',
            'text':
            _ssh_help(configuration, client_id, cloud_id, saved_instance,
                      instance_id)
        })

    elif action in ("softrestart", "hardrestart"):
        boot_strength = action.replace("restart", "").upper()
        saved_instance = cloud_load_instance(configuration, client_id,
                                             cloud_id, instance_id)
        if not saved_instance:
            logger.error("no saved %s cloud instance %s for %s to restart" %
                         (cloud_id, instance_id, client_id))
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                instance_missing_msg % (_label, service_title)
            })
            return (output_objects, returnvalues.CLIENT_ERROR)

        (action_status,
         action_msg) = restart_cloud_instance(configuration, client_id,
                                              cloud_id, cloud_flavor,
                                              instance_id, boot_strength)
        if not action_status:
            logger.error(
                "%s %s cloud instance %s for %s failed: %s" %
                (action, cloud_id, instance_id, client_id, action_msg))
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'Your %s instance %s at %s did not succeed: %s' %
                (action, _label, service_title, action_msg)
            })
            return (output_objects, returnvalues.SYSTEM_ERROR)

        output_objects.append({
            'object_type':
            'text',
            'text':
            "%s instance %s at %s: %s" %
            (action, _label, service_title, "success")
        })
        output_objects.append({
            'object_type':
            'html_form',
            'text':
            _ssh_help(configuration, client_id, cloud_id, saved_instance,
                      instance_id)
        })

    elif "stop" == action:
        saved_instance = cloud_load_instance(configuration, client_id,
                                             cloud_id, instance_id)
        if not saved_instance:
            logger.error("no saved %s cloud instance %s for %s to %s" %
                         (cloud_id, instance_id, client_id, action))
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                instance_missing_msg % (_label, service_title)
            })
            return (output_objects, returnvalues.CLIENT_ERROR)

        (action_status,
         action_msg) = stop_cloud_instance(configuration, client_id, cloud_id,
                                           cloud_flavor, instance_id)
        if not action_status:
            logger.error(
                "%s %s cloud instance %s for %s failed: %s" %
                (action, cloud_id, instance_id, client_id, action_msg))
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'Your %s instance %s at %s did not succeed: %s' %
                (action, _label, service_title, action_msg)
            })
            return (output_objects, returnvalues.SYSTEM_ERROR)

        output_objects.append({
            'object_type':
            'text',
            'text':
            "%s instance %s at %s: %s" %
            (action, _label, service_title, "success")
        })

    elif "webaccess" == action:
        saved_instance = cloud_load_instance(configuration, client_id,
                                             cloud_id, instance_id)
        if not saved_instance:
            logger.error("no saved %s cloud instance %s for %s to query" %
                         (cloud_id, instance_id, client_id))
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                instance_missing_msg % (_label, service_title)
            })
            return (output_objects, returnvalues.CLIENT_ERROR)

        if not configuration.user_cloud_console_access:
            logger.error("web console not enabled in conf!")
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'Site does not expose cloud web console!'
            })
            return (output_objects, returnvalues.CLIENT_ERROR)

        (action_status,
         action_msg) = web_access_cloud_instance(configuration, client_id,
                                                 cloud_id, cloud_flavor,
                                                 instance_id)
        if not action_status:
            logger.error(
                "%s %s cloud instance %s for %s failed: %s" %
                (action, service_title, instance_id, client_id, action_msg))
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'Your %s instance %s at %s did not succeed: %s' %
                (action, _label, service_title, action_msg)
            })
            return (output_objects, returnvalues.SYSTEM_ERROR)

        output_objects.append({
            'object_type':
            'text',
            'text':
            "%s instance %s at %s" % (action, _label, service_title)
        })
        output_objects.append({
            'object_type': 'link',
            'destination': action_msg,
            'text': 'Open web console',
            'class': 'consolelink iconspace',
            'title': 'open web console',
            'target': '_blank'
        })
        output_objects.append({'object_type': 'text', 'text': ''})
        output_objects.append({
            'object_type':
            'html_form',
            'text':
            _ssh_help(configuration, client_id, cloud_id, saved_instance,
                      instance_id)
        })

    elif "updatekeys" == action:
        saved_instance = cloud_load_instance(configuration, client_id,
                                             cloud_id, instance_id)
        if not saved_instance:
            logger.error("no saved %s cloud instance %s for %s to update" %
                         (cloud_id, instance_id, client_id))
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                instance_missing_msg % (_label, service_title)
            })
            return (output_objects, returnvalues.CLIENT_ERROR)

        cloud_settings = load_cloud(client_id, configuration)
        auth_keys = cloud_settings.get('authkeys', '').split('\n')
        (action_status,
         action_msg) = update_cloud_instance_keys(configuration, client_id,
                                                  cloud_id, cloud_flavor,
                                                  instance_id, auth_keys)
        if not action_status:
            logger.error(
                "%s %s cloud instance %s for %s failed: %s" %
                (action, cloud_id, instance_id, client_id, action_msg))
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'Your %s instance %s at %s did not succeed: %s' %
                (action, _label, service_title, action_msg)
            })
            return (output_objects, returnvalues.SYSTEM_ERROR)

        output_objects.append({
            'object_type':
            'text',
            'text':
            "%s instance %s at %s: %s" %
            (action, _label, service_title, "success")
        })
        output_objects.append({
            'object_type':
            'html_form',
            'text':
            _ssh_help(configuration, client_id, cloud_id, saved_instance,
                      instance_id)
        })
        output_objects.append({'object_type': 'text', 'text': ssh_auth_msg})
        for pub_key in auth_keys:
            output_objects.append({'object_type': 'text', 'text': pub_key})

    else:
        output_objects.append({
            'object_type': 'error_text',
            'text': 'Unknown action: %s' % action
        })
        return_status = returnvalues.CLIENT_ERROR

    output_objects.append({
        'object_type': 'link',
        'destination': 'cloud.py',
        'class': 'backlink iconspace',
        'title': 'Go back to cloud management',
        'text': 'Back to cloud management'
    })
    return (output_objects, return_status)
Пример #7
0
def main(client_id, user_arguments_dict):
    """Main function used by front end"""
    (configuration, logger, output_objects, op_name) = \
        initialize_main_variables(client_id, op_header=False)
    defaults = signature()[1]
    (validate_status, accepted) = validate_input_and_cert(
        user_arguments_dict,
        defaults,
        output_objects,
        client_id,
        configuration,
        allow_rejects=False,
    )
    if not validate_status:
        return (accepted, returnvalues.CLIENT_ERROR)

    title_entry = find_entry(output_objects, 'title')
    title_entry['text'] = 'Dashboard'

    # jquery support for tablesorter and confirmation on "leave":

    add_import, add_init, add_ready = '', '', ''
    add_init += '''
              function roundNumber(num, dec) {
              var result = Math.round(num*Math.pow(10,dec))/Math.pow(10,dec);
              return result;
          }
    '''
    add_ready += '''
          $("#jobs_stats").addClass("spinner iconleftpad");
          $("#jobs_stats").html("Loading job stats...");
          $("#res_stats").addClass("spinner iconleftpad");
          $("#res_stats").html("Loading resource stats...");
          $("#disk_stats").addClass("spinner iconleftpad");
          $("#disk_stats").html("Loading disk stats...");
          $("#cert_stats").addClass("spinner iconleftpad");
          $("#cert_stats").html("Loading certificate information...");
          /* Run certificate request in the background and handle as soon as results come in */
          $.ajax({
              url: "userstats.py?output_format=json;stats=certificate",
              type: "GET",
              dataType: "json",
              cache: false,
              success: function(jsonRes, textStatus) {
                  var i = 0;
                  var certificate = null;
                  var renew_days = 30;
                  var day_msecs = 24*60*60*1000;
                  // Grab results from json response and place them in resource status.
                  for(i=0; i<jsonRes.length; i++) {
                      if (jsonRes[i].object_type == "user_stats") {    
                          certificate = jsonRes[i].certificate;
                          $("#cert_stats").removeClass("spinner iconleftpad");
                          $("#cert_stats").empty();
                          if (certificate.expire == -1) {
                              break;
                          }
                          var expire_date = new Date(certificate.expire);
                          $("#cert_stats").append("Your user certificate expires on " +
                          expire_date + ".");
                          // Use date from time diff in ms to avoid calendar mangling
                          var show_renew = new Date(expire_date.getTime() - renew_days*day_msecs);
                          if(new Date().getTime() > show_renew.getTime()) {
                              $("#cert_stats").addClass("warningtext");
                              $("#cert_stats").append("&nbsp;<a class=\'certrenewlink  iconspace\' href=\'reqcert.py\'>Renew certificate</a>.");
                          }
                          break;
                      }
                  }
              }
          });
          /* Run jobs request in the background and handle as soon as results come in */
          $.ajax({
              url: "userstats.py?output_format=json;stats=jobs",
              type: "GET",
              dataType: "json",
              cache: false,
              success: function(jsonRes, textStatus) {
                  var i = 0;
                  var jobs = null;
                  // Grab results from json response and place them in job status.
                  for(i=0; i<jsonRes.length; i++) {
                      if (jsonRes[i].object_type == "user_stats") {    
                          jobs = jsonRes[i].jobs;
                          //alert("inspect stats result: " + jobs);
                          $("#jobs_stats").removeClass("spinner iconleftpad");
                          $("#jobs_stats").empty();
                          $("#jobs_stats").append("You have submitted a total of " + jobs.total +
                              " jobs: " + jobs.parse + " parse, " + jobs.queued + " queued, " +
                              jobs.frozen + " frozen, " + jobs.executing + " executing, " +
                              jobs.finished + " finished, " + jobs.retry + " retry, " +
                              jobs.canceled + " canceled, " + jobs.expired + " expired and " +
                              jobs.failed + " failed.");
                         break;
                      }
                  }   
              }
          });
          /* Run resources request in the background and handle as soon as results come in */
          $.ajax({
              url: "userstats.py?output_format=json;stats=resources",
              type: "GET",
              dataType: "json",
              cache: false,
              success: function(jsonRes, textStatus) {
                  var i = 0;
                  var resources = null;
                  // Grab results from json response and place them in resource status.
                  for(i=0; i<jsonRes.length; i++) {
                      if (jsonRes[i].object_type == "user_stats") {    
                          resources = jsonRes[i].resources;
                          //alert("inspect resources stats result: " + resources);
                          $("#res_stats").removeClass("spinner iconleftpad");
                          $("#res_stats").empty();
                          $("#res_stats").append(resources.resources + " resources providing " +
                          resources.exes + " execution units in total allow execution of your jobs.");
                          break;
                      }
                  }
              }
          });
          /* Run disk request in the background and handle as soon as results come in */
          $.ajax({
              url:"userstats.py?output_format=json;stats=disk",
              type: "GET",
              dataType: "json",
              cache: false,
              success: function(jsonRes, textStatus) {
                  var i = 0;
                  var disk = null;
                  // Grab results from json response and place them in resource status.
                  for(i=0; i<jsonRes.length; i++) {
                      if (jsonRes[i].object_type == "user_stats") {    
                          disk = jsonRes[i].disk;
                          //alert("inspect disk stats result: " + disk);
                          $("#disk_stats").removeClass("spinner iconleftpad");
                          $("#disk_stats").empty();
                          $("#disk_stats").append("Your own " + disk.own_files +" files and " +
                              disk.own_directories + " directories take up " + roundNumber(disk.own_megabytes, 2) +
                              " MB in total and you additionally share " + disk.vgrid_files +
                              " files and " + disk.vgrid_directories + " directories of " +
                              roundNumber(disk.vgrid_megabytes, 2) + " MB in total.");
                          break;
                      }
                  }
              }
          });
    '''
    title_entry['script']['advanced'] += add_import
    title_entry['script']['init'] += add_init
    title_entry['script']['ready'] += add_ready

    output_objects.append({'object_type': 'header', 'text': 'Dashboard'})
    output_objects.append({
        'object_type':
        'sectionheader',
        'text':
        "Welcome to the %s" % configuration.site_title
    })
    welcome_line = "Hi %s" % extract_field(client_id, "full_name")
    output_objects.append({'object_type': 'text', 'text': welcome_line})
    dashboard_info = """
This is your private entry page or your dashboard where you can get a
quick status overview and find pointers to help and documentation.
When you are logged in with your user credentials/certificate, as you are now,
you can navigate your pages using the menu on the left.
""" % os.environ
    output_objects.append({'object_type': 'text', 'text': dashboard_info})

    output_objects.append({
        'object_type': 'sectionheader',
        'text': "Your Status"
    })
    output_objects.append({
        'object_type':
        'html_form',
        'text':
        '''
<p>
This is a general status overview for your Grid activities. Please note that some
of the numbers are cached for a while to keep server load down.
</p>
<div id="jobs_stats"><!-- for jquery --></div><br />
<div id="res_stats"><!-- for jquery --></div><br />
<div id="disk_stats"><!-- for jquery --></div><br />
<div id="cert_stats"><!-- for jquery --></div><br />
<div id="cert_renew" class="hidden"><a href="reqcert.py">renew certificate</a>
</div>
'''
    })

    output_objects.append({
        'object_type': 'sectionheader',
        'text': 'Documentation and Help'
    })
    online_help = """
%s includes some built-in documentation like the
""" % configuration.site_title
    output_objects.append({'object_type': 'text', 'text': online_help})
    output_objects.append({
        'object_type': 'link',
        'destination': 'docs.py',
        'class': 'infolink iconspace',
        'title': 'built-in documentation',
        'text': 'Docs page'
    })
    project_info = """
but additional help, background information and tutorials are available in the
"""
    output_objects.append({'object_type': 'text', 'text': project_info})
    output_objects.append({
        'object_type':
        'link',
        'destination':
        configuration.site_external_doc,
        'class':
        'urllink iconspace',
        'title':
        'external documentation',
        'text':
        'external %s documentation' % configuration.site_title
    })

    output_objects.append({
        'object_type': 'sectionheader',
        'text': "Personal Settings"
    })
    settings_info = """
You can customize your personal pages by opening the Settings
page from the navigation menu and entering personal preferences. In that way you
can completely redecorate your interface and configure things like notification,
profile visibility and remote file access.
"""
    output_objects.append({'object_type': 'text', 'text': settings_info})

    #env_info = """Env %s""" % os.environ
    #output_objects.append({'object_type': 'text', 'text': env_info})

    return (output_objects, returnvalues.OK)
Пример #8
0
def main(client_id, user_arguments_dict):
    """Main function used by front end"""

    (configuration, logger, output_objects, op_name) = \
        initialize_main_variables(client_id, op_header=False)
    defaults = signature()[1]
    title_entry = find_entry(output_objects, 'title')
    title_entry['text'] = 'People'
    (validate_status, accepted) = validate_input_and_cert(
        user_arguments_dict,
        defaults,
        output_objects,
        client_id,
        configuration,
        allow_rejects=False,
    )
    if not validate_status:
        return (accepted, returnvalues.CLIENT_ERROR)

    operation = accepted['operation'][-1]
    caching = (accepted['caching'][-1].lower() in ('true', 'yes'))

    if not operation in allowed_operations:
        output_objects.append({
            'object_type':
            'text',
            'text':
            '''Operation must be one of %s.''' % ', '.join(allowed_operations)
        })
        return (output_objects, returnvalues.OK)

    logger.info("%s %s begin for %s" % (op_name, operation, client_id))
    pending_updates = False
    if operation in show_operations:

        # jquery support for tablesorter and confirmation on "send"
        # table initially sorted by 0 (name)

        # NOTE: We distinguish between caching on page load and forced refresh
        refresh_helper = 'ajax_people(%s, %%s)'
        refresh_call = refresh_helper % configuration.notify_protocols
        table_spec = {
            'table_id': 'usertable',
            'sort_order': '[[0,0]]',
            'refresh_call': refresh_call % 'false'
        }
        (add_import, add_init,
         add_ready) = man_base_js(configuration, [table_spec], {'width': 640})
        if operation == "show":
            add_ready += '%s;' % (refresh_call % 'true')
        title_entry['script']['advanced'] += add_import
        title_entry['script']['init'] += add_init
        title_entry['script']['ready'] += add_ready

        output_objects.append({
            'object_type': 'html_form',
            'text': man_base_html(configuration)
        })

        output_objects.append({'object_type': 'header', 'text': 'People'})

        output_objects.append({
            'object_type': 'text',
            'text': 'View and communicate with other users.'
        })

        output_objects.append({
            'object_type': 'sectionheader',
            'text': 'All users'
        })

        # Helper form for sends

        form_method = 'post'
        csrf_limit = get_csrf_limit(configuration)
        target_op = 'sendrequestaction'
        csrf_token = make_csrf_token(configuration, form_method, target_op,
                                     client_id, csrf_limit)
        helper = html_post_helper(
            'sendmsg', '%s.py' % target_op, {
                'cert_id': '__DYNAMIC__',
                'protocol': '__DYNAMIC__',
                'request_type': 'plain',
                'request_text': '',
                csrf_field: csrf_token
            })
        output_objects.append({'object_type': 'html_form', 'text': helper})

        output_objects.append({
            'object_type': 'table_pager',
            'entry_name': 'people',
            'default_entries': default_pager_entries
        })

    users = []
    if operation in list_operations:
        logger.info("get vgrid and user map with caching %s" % caching)
        visible_user = user_visible_user_confs(configuration, client_id,
                                               caching)
        vgrid_access = user_vgrid_access(configuration,
                                         client_id,
                                         caching=caching)
        anon_map = anon_to_real_user_map(configuration)
        if not visible_user:
            output_objects.append({
                'object_type': 'error_text',
                'text': 'no users found!'
            })
            return (output_objects, returnvalues.SYSTEM_ERROR)

        # NOTE: use simple pending check if caching to avoid lock during update
        if caching:
            pending_updates = pending_vgrids_update(configuration) or \
                pending_users_update(configuration)
        else:
            pending_updates = False
        if pending_updates:
            logger.debug("found pending cache updates: %s" % pending_updates)
        else:
            logger.debug("no pending cache updates")

        for (visible_user_id, user_dict) in visible_user.items():
            user_id = visible_user_id
            if visible_user_id in anon_map.keys():
                # Maintain user anonymity
                pretty_id = 'Anonymous user with unique ID %s' % visible_user_id
                user_id = anon_map[visible_user_id]
            else:
                # Show user-friendly version of user ID
                hide_email = user_dict.get(CONF,
                                           {}).get('HIDE_EMAIL_ADDRESS', True)
                pretty_id = pretty_format_user(user_id, hide_email)
            anon_img = "%s/anonymous.png" % configuration.site_images
            avatar_size = 32
            user_obj = {
                'object_type': 'user',
                'name': visible_user_id,
                'pretty_id': pretty_id,
                'avatar_url': anon_img
            }
            user_obj.update(user_dict)
            # NOTE: datetime is not json-serializable so we force to string
            created = user_obj.get(CONF, {}).get('CREATED_TIMESTAMP', '')
            if created:
                user_obj[CONF]['CREATED_TIMESTAMP'] = str(created)
            # TODO: consider ALWAYS using anon_id format in link here
            user_obj['userdetailslink'] = \
                {'object_type': 'link',
                 'destination':
                 'viewuser.py?cert_id=%s'
                 % quote(visible_user_id),
                 'class': 'infolink iconspace',
                 'title': 'View details for %s' %
                 visible_user_id,
                 'text': ''}
            vgrids_allow_email = user_dict[CONF].get('VGRIDS_ALLOW_EMAIL', [])
            vgrids_allow_im = user_dict[CONF].get('VGRIDS_ALLOW_IM', [])
            if any_vgrid in vgrids_allow_email:
                email_vgrids = vgrid_access
            else:
                email_vgrids = set(vgrids_allow_email).intersection(
                    vgrid_access)

            if email_vgrids and configuration.site_enable_gravatars:
                visible_email = extract_field(user_id, 'email')
                user_obj['avatar_url'] = user_gravatar_url(
                    configuration, visible_email, avatar_size)

            if any_vgrid in vgrids_allow_im:
                im_vgrids = vgrid_access
            else:
                im_vgrids = set(vgrids_allow_im).intersection(vgrid_access)
            for proto in configuration.notify_protocols:
                if not email_vgrids and proto == 'email':
                    continue
                if not im_vgrids and proto != 'email':
                    continue
                if user_obj[CONF].get(proto.upper(), None):
                    link = 'send%slink' % proto
                    user_obj[link] = {
                        'object_type':
                        'link',
                        'destination':
                        "javascript: confirmDialog(%s, '%s', '%s', %s);" %
                        ('sendmsg', 'Really send %s message to %s?' %
                         (proto, visible_user_id), 'request_text',
                         "{cert_id: '%s', 'protocol': '%s'}" %
                         (visible_user_id, proto)),
                        'class':
                        "%s iconspace" % link,
                        'title':
                        'Send %s message to %s' % (proto, visible_user_id),
                        'text':
                        ''
                    }
            logger.debug("append user %s" % user_obj)
            users.append(user_obj)

    if operation == "show":
        # insert dummy placeholder to build table
        user_obj = {'object_type': 'user', 'name': ''}
        users.append(user_obj)

    output_objects.append({
        'object_type': 'user_list',
        'pending_updates': pending_updates,
        'users': users
    })

    logger.info("%s %s end for %s" % (op_name, operation, client_id))
    return (output_objects, returnvalues.OK)