예제 #1
0
def update_pickled_dict(path, changes):
    """Update pickled dictionary on disk with provided changes"""

    saved_dict = load(path)
    saved_dict.update(changes)
    dump(saved_dict, path)
    return saved_dict
예제 #2
0
def manage_pending_peers(configuration, client_id, action, change_list):
    """Helper to manage changes to pending peers list of client_id"""
    _logger = configuration.logger
    client_dir = client_id_dir(client_id)
    pending_peers_path = os.path.join(configuration.user_settings, client_dir,
                                      pending_peers_filename)
    try:
        pending_peers = load(pending_peers_path)
    except Exception as exc:
        if os.path.exists(pending_peers_path):
            _logger.warning("could not load pending peers from %s: %s" %
                            (pending_peers_path, exc))
        pending_peers = []
    change_dict = dict(change_list)
    # NOTE: always remove old first to replace any existing and move them last
    pending_peers = [(i, j)
                     for (i, j) in pending_peers if not i in change_dict]
    if action == "add":
        pending_peers += change_list
    elif action == "remove":
        pass
    else:
        _logger.error(
            "unsupported action in manage pending peers: %s" % action)
        return False
    try:
        dump(pending_peers, pending_peers_path)
        return True
    except Exception as exc:
        _logger.warning("could not save pending peers to %s: %s" %
                        (pending_peers_path, exc))
        return False
예제 #3
0
def filter_pickled_list(path, changes):
    """Filter pickled list on disk with provided changes where changes is a
    dictionary mapping existing list entries and the value to replace it with.
    """

    saved_list = load(path)
    saved_list = [changes.get(entry, entry) for entry in saved_list]
    dump(saved_list, path)
    return saved_list
예제 #4
0
def get_account_req(req_id, configuration):
    """Helper to fetch dictionary for a pending account request"""
    req_path = os.path.join(configuration.user_pending, req_id)
    req_dict = load(req_path)
    if not req_dict:
        return (False, 'Could not open account request %s' % req_id)
    else:
        req_dict['id'] = req_id
        req_dict['created'] = os.path.getctime(req_path)
        return (True, req_dict)
예제 #5
0
def unpickle(path, logger, allow_missing=False):
    """Unpack pickled object in path"""
    try:
        data_object = load(path)
        logger.debug('%s was unpickled successfully' % path)
        return data_object
    except Exception as err:
        # NOTE: check that it was in fact due to file does not exist error
        if not allow_missing or getattr(err, 'errno', None) != errno.ENOENT:
            logger.error('%s could not be opened/unpickled! %s' % (path, err))
        return False
예제 #6
0
def filter_pickled_dict(path, changes):
    """Filter pickled dictionary on disk with provided changes where changes
    is a dictionary mapping existing dictionary values to a value to replace
    it with.
    """

    saved_dict = load(path)
    for (key, val) in saved_dict.items():
        if val in changes.keys():
            saved_dict[key] = changes[val]
    dump(saved_dict, path)
    return saved_dict
예제 #7
0
def load_json(path, logger, allow_missing=False, convert_utf8=True):
    """Unpack json object in path"""
    try:
        data_object = load(path, serializer='json')
        logger.debug('%s was loaded successfully' % path)
        if convert_utf8:
            data_object = force_utf8_rec(data_object)
        return data_object
    except Exception as err:
        # NOTE: check that it was in fact due to file does not exist error
        if not allow_missing or getattr(err, 'errno', None) != errno.ENOENT:
            logger.error('%s could not be opened/loaded! %s' % (path, err))
        return False
예제 #8
0
def is_user(entity_id, mig_server_home):
    """Check if user exists in database"""

    result = False

    db_path = os.path.join(mig_server_home, user_db_filename)
    try:
        user_db = load(db_path)
        if entity_id in user_db:
            result = True
    except:
        pass

    return result
예제 #9
0
def get_accepted_peers(configuration, client_id):
    """Helper to get the list of peers accepted by client_id"""
    _logger = configuration.logger
    client_dir = client_id_dir(client_id)
    peers_path = os.path.join(configuration.user_settings, client_dir,
                              peers_filename)
    try:
        accepted_peers = load(peers_path)
    except Exception as exc:
        if os.path.exists(peers_path):
            _logger.warning("could not load peers from %s: %s" %
                            (peers_path, exc))
        accepted_peers = {}
    return accepted_peers
예제 #10
0
def load_access_request(configuration, request_dir, req_name):
    """Load request req_name with predefined file extension for given
    request_dir.
    """
    request = None
    req_path = os.path.join(request_dir, req_name)
    try:
        if not req_name.startswith(request_prefix) or \
               not req_name.endswith(request_ext):
            raise ValueError("invalid request name: %s" % req_name)
        request = load(req_path)
    except Exception as err:
        configuration.logger.error("could not load request in %s: %s" % \
                                   (req_path, err))
    return request
예제 #11
0
 if verbose:
     print('stats for archives in state %r between %s and %s' %
           (state, after, before))
 retval = 0
 configuration = get_configuration_object(skip_log=True)
 meta_pattern = os.path.join(configuration.freeze_home,
                             '+C=*emailAddress=*', 'archive-*', 'meta.pck')
 csv = ['USER;ID;FLAVOR;CREATED;ON_DISK;TAPE_PROMISE']
 for meta_path in glob.glob(meta_pattern):
     #print "DEBUG: found meta in %s" % meta_path
     meta_timestamp = os.path.getmtime(meta_path)
     if meta_timestamp >= created_before or meta_timestamp <= created_after:
         # print "DEBUG: filter meta in %s due to timestamp %s" % (
         #    meta_path, meta_timestamp)
         continue
     meta_dict = load(meta_path)
     helper = {
         'CREATOR': 'UNSET',
         'ID': 'UNSET',
         'STATE': 'UNSET',
         'CREATED_TIMESTAMP': 'UNSET',
         'LOCATION': 'UNSET',
         'DISK_TIME': 'UNKNOWN',
         'TAPE_PROMISE': 'UNKNOWN'
     }
     helper.update(meta_dict)
     if helper['STATE'] != state:
         #print "DEBUG: filter meta with state %(STATE)s" % helper
         continue
     #print "DEBUG: dict %s" % helper
     helper['LOCATION'] = helper.get('LOCATION', [])
예제 #12
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)
예제 #13
0
from __future__ import print_function
from __future__ import absolute_import

import os
import sys

from mig.shared.conf import get_configuration_object
from mig.shared.serial import load

configuration = get_configuration_object()

sandboxdb_file = configuration.sandbox_home + os.sep\
     + 'sandbox_users.pkl'

userdb = None
if not os.path.isfile(sandboxdb_file):
    print('%s is not an existing file!' % sandboxdb_file)
    sys.exit(1)

try:
    userdb = load(sandboxdb_file)
except Exception as exc:
    print('Exception reading %s, (%s)' % (sandboxdb_file, exc))
user_count = 0

for (key, value) in userdb.items():
    print(key, ':', value)
    user_count += 1

print('Total number of registered users: %d' % user_count)
예제 #14
0
    if args:
        try:
            peer_dict['full_name'] = args[0]
            peer_dict['organization'] = args[1]
            peer_dict['state'] = args[2]
            peer_dict['country'] = args[3]
            peer_dict['email'] = args[4]
            peer_dict['comment'] = args[5]
        except IndexError:
            print('Error: too few arguments given (expected 6 got %d)' %
                  len(args))
            usage()
            sys.exit(1)
    elif user_file:
        try:
            peer_dict = load(user_file)
        except Exception as err:
            print('Error in user name extraction: %s' % err)
            usage()
            sys.exit(1)
    else:
        print('No peer specified: please pass peer as args or with -u PATH')
        usage()
        sys.exit(1)

    fill_distinguished_name(peer_dict)
    peer_id = peer_dict['distinguished_name']

    if search_filter['email'] == keyword_auto:
        peer_emails = valid_email_addresses(configuration,
                                            peer_dict['comment'])
예제 #15
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]
    client_dir = client_id_dir(client_id)
    title_entry = find_entry(output_objects, 'title')
    title_entry['text'] = 'Peers'
    (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.info("%s begin for %s" % (op_name, client_id))

    # IMPORTANT: single line here to avoid breaking javascript inlining
    expire_help = "For security reasons peer accounts should be closed when no longer required. Expire is used to limit account access time for that purpose, and you can always extend it later if needed. For courses and workshops a few weeks or months should usually suffice, while projects and long-term collaboration often extend to months or years. Peer accounts still need to be renewed at least annually, but the peer users can do so themselves without your repeated explicit acceptance, as long as it does not exceed your provided expire date."

    # jquery support for tablesorter and confirmation on delete
    # table initially sorted by col. 5 (kind), then 0 (name)
    refresh_call = 'ajax_peers()'
    table_spec = {'table_id': 'peers', 'sort_order':
                  '[[5,0],[0,0]]',
                  'refresh_call': refresh_call}
    (add_import, add_init, add_ready) = man_base_js(configuration,
                                                    [table_spec])

    add_init += '''
/* Helper to define countries for which State field makes sense */
var enable_state = ["US", "CA", "AU"];

function show_info(title, msg) {
    $("#info_dialog").dialog("option", "title", title);
    $("#info_dialog").html("<p>"+msg+"</p>");
    $("#info_dialog").dialog("open");
}

function toggle_state() {
    $("#fields-tab .save_peers .field_group").each(function() {
        var country = $(this).find(".entry-field.country").val();
        if (country && enable_state.indexOf(country) > -1) {
            //console.debug("unlock state for "+country);
            $(this).find("input.entry-field.state").prop("readonly", false);
        } else {
            //console.debug("lock state for "+country);
            $(this).find("input.entry-field.state").prop("readonly", true);
            /* NOTE: reset state on change to other country */
            $(this).find("input.entry-field.state").val("");
        }
      }
    );
}

function transfer_id_fields() {
    //console.log("in transfer_id_fields");
    var peer_count = 0;
    var peer_id;
    $("#fields-tab .save_peers .field_group").each(function() {
        var group = $(this);
        peer_id = '';
        var full_name = $(group).find("input.entry-field.full_name").val();
        var organization = $(group).find("input.entry-field.organization").val();
        var email = $(group).find("input.entry-field.email").val();
        var country = $(group).find(".entry-field.country").val();
        var state = $(group).find("input.entry-field.state").val();
        if (!state) {
            state = "NA"
        }
        if (full_name && organization && email && country && state) {
            peer_id = "/C="+country+"/ST="+state+"/L=NA/O="+ \
                organization+"/OU=NA/CN="+full_name+"/emailAddress="+email;
            //console.debug("built peer_id: "+peer_id);
            peer_count += 1;
        }
        /* Always set peer_id to reset empty rows */
        $(group).find("input.id-collector").val(peer_id);
        console.log("set collected peer_id: "+$(group).find("input.id-collector").val());
    });
    if (peer_count > 0) {
        return true;
    } else {
        return false;
    }
}

    '''
    add_ready += '''
        $(".peers-tabs").tabs();
        $(".peers-tabs .accordion").accordion({
            collapsible: true,
            active: false,
            icons: {"header": "ui-icon-plus", "activeHeader": "ui-icon-minus"},
            heightStyle: "content"
            });
        /* fix and reduce accordion spacing */
        $(".peers-tabs .accordion .ui-accordion-header").css("padding-top", 0).css("padding-bottom", 0).css("margin", 0);
        $(".peers-tabs .init-expanded.accordion ").accordion("option", "active", 0);
        $("#fields-tab .save_peers").on("submit", transfer_id_fields);
        $("#info_dialog").dialog(
              { autoOpen: false,
                width: 500,
                modal: true,
                closeOnEscape: true,

                buttons: { "Ok": function() { $(this).dialog("close"); }}
              });
    '''
    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': 'Peers'})

    user_map = get_full_user_map(configuration)
    user_dict = user_map.get(client_id, None)
    # Optional site-wide limitation of peers permission
    peers_permit_class = 'hidden'
    if user_dict and peers_permit_allowed(configuration, user_dict):
        peers_permit_class = ''

    form_method = 'post'
    csrf_limit = get_csrf_limit(configuration)
    target_op = 'peersaction'
    csrf_token = make_csrf_token(configuration, form_method, target_op,
                                 client_id, csrf_limit)
    fill_helpers = {'peers_notice': configuration.site_peers_notice,
                    'site': configuration.short_title,
                    'peers_permit_class': peers_permit_class,
                    'form_method': form_method,
                    'csrf_field': csrf_field, 'csrf_limit': csrf_limit,
                    'target_op': target_op, 'csrf_token': csrf_token,
                    'expire_help': expire_help,
                    'csv_header': csv_sep.join([i for i in peers_fields])}
    form_prefix_html = '''
<form class="save_peers save_general" method="%(form_method)s"
action="%(target_op)s.py">
'''
    form_suffix_html = '''
<input type="submit" value="Save Peers" /><br/>
</form>
'''
    form_accept_html = '''
<input type="submit" value="Apply" /><br/>
</form>
'''
    shared_peer_html = '''
    <input type="hidden" name="%(csrf_field)s" value="%(csrf_token)s" />
    <div class="form-row three-col-grid">
      <div class="col-md-4 mb-3 form-cell">
          <label for="peers_label">Label</label>
          <input class="form-control fill-width" type="text" name="peers_label"
            value="" pattern="[^ ]*" title="Label for peers"
            placeholder="Peers name or label" />
      </div>
      <div class="col-md-4 mb-3 form-cell">
          <label for="peers_kind">Kind</label>
          <select class="form-control themed-select html-select" name="peers_kind">
'''
    for name in peer_kinds:
        shared_peer_html += '''
              <option value="%s">%s
''' % (name, name.capitalize())
    shared_peer_html += '''
          </select>
      </div>
      <div class="col-md-4 mb-3 form-cell">
          <label for="peers_expire">Expire&nbsp;
            <span class="info leftpad iconspace" title="%(expire_help)s"
                onClick="show_info(\'Expire Help\', \'%(expire_help)s\');"/>
          </label>
          <input class="form-control themed-select html-select fill-width"
            type="date" name="peers_expire" required pattern="[0-9/-]+"
            title="Access expiry date" />
      </div>
    </div>
'''
    fill_helpers['form_prefix_html'] = form_prefix_html % fill_helpers
    fill_helpers['form_suffix_html'] = form_suffix_html % fill_helpers
    fill_helpers['form_accept_html'] = form_accept_html % fill_helpers
    fill_helpers['shared_peer_html'] = shared_peer_html % fill_helpers

    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: %s" % (peers_path, exc))
        all_peers = {}

    pending_peers_path = os.path.join(configuration.user_settings, client_dir,
                                      pending_peers_filename)
    try:
        pending_peers = load(pending_peers_path)
    except Exception as exc:
        logger.warning("could not load pending peers from %s: %s" %
                       (pending_peers_path, exc))
        pending_peers = []

    tabs_html = '''
<div id="info_dialog" class="hidden"></div>
<div id="wrap-tabs" class="peers-tabs">
<ul>
<li><a href="#show-tab">Show Peers</a></li>
<li class="%(peers_permit_class)s"><a href="#requests-tab">Requested Peers</a></li>
<li class="%(peers_permit_class)s"><a href="#fields-tab">Enter Peers</a></li>
<li class="%(peers_permit_class)s"><a href="#import-tab">Import Peers</a></li>
<!-- TODO: enable additional methods when ready? -->
<li class="%(peers_permit_class)s hidden"><a href="#urlfetch-tab">Fetch Peers From URL</a></li>
</ul>

<div id="show-tab">
<p>
%(peers_notice)s
This is an overview of your registered peers. That is, people that you have
vouched for to get an account on %(site)s because they need it for a particular
course/workshop, research project or for general long term collaboration with
you. The site admins will use this information to accept account requests and
extensions from your peers until the given time of expiry.
</p>
<div class="peer_entries">
'''
    output_objects.append(
        {'object_type': 'html_form', 'text': tabs_html % fill_helpers})

    output_objects.append({'object_type': 'table_pager', 'entry_name': 'peers',
                           'default_entries': default_pager_entries})
    peers = []
    for (peer_id, entry) in all_peers.items():
        filled_entry = dict([(field, '') for field in
                             ('label', 'kind', 'expire')])
        fill_distinguished_name(entry)
        filled_entry.update(entry)
        filled_entry['anon_peer_id'] = anon_user_id(peer_id)
        filled_entry['object_type'] = 'peer'
        # NOTE: very simple edit dialog to change only expire through confirm.
        # We could add similar buttons for kind and label fields but they can
        # be edited with Update in Edit Peers until we make a dedicated dialog
        filled_entry['editpeerlink'] = {
            'object_type': 'link',
            'destination':
            "javascript: confirmDialog(%s, '%s', '%s', %s);" %
            ('peer_action', 'Update %(full_name)s (%(email)s) expire date (YYYY-MM-DD)?' % filled_entry,
             'peers_expire',
             "{action: 'update', peers_label: '%(label)s', peers_kind: '%(kind)s', peers_content: '%(distinguished_name)s', peers_invite: false}" % filled_entry),
            'class': 'editlink iconspace',
            'title': 'Update %(distinguished_name)s Expire value in peers' % filled_entry,
            'text': ''}
        filled_entry['invitepeerlink'] = {
            'object_type': 'link',
            'destination':
            "javascript: confirmDialog(%s, '%s', %s, %s);" %
            ('peer_action', 'Send invitation email to %(distinguished_name)s?' % filled_entry,
             'undefined',
             "{action: 'update', peers_label: '%(label)s', peers_kind: '%(kind)s', peers_expire:'%(expire)s', peers_content: '%(distinguished_name)s', peers_invite: true}" % filled_entry),
            'class': 'invitelink iconspace',
            'title': 'Invite %(distinguished_name)s as peer' % filled_entry,
            'text': ''}
        filled_entry['viewpeerlink'] = {
            'object_type': 'link',
            'destination': 'viewuser.py?cert_id=%(anon_peer_id)s' % filled_entry,
            'class': 'userlink iconspace',
            'title': 'Lookup %(distinguished_name)s user details' % filled_entry,
            'text': ''}
        filled_entry['delpeerlink'] = {
            'object_type': 'link',
            'destination':
            "javascript: confirmDialog(%s, '%s', %s, %s);" %
            ('peer_action', 'Really remove %(distinguished_name)s?' % filled_entry,
             'undefined',
             "{action: 'remove', peers_label: '%(label)s', peers_kind: '%(kind)s', peers_expire:'%(expire)s', peers_content: '%(distinguished_name)s', peers_invite: false}" % filled_entry),
            'class': 'removelink iconspace',
            'title': 'Remove %(distinguished_name)s from peers' % filled_entry,
            'text': ''}
        peers.append(filled_entry)
    output_objects.append({'object_type': 'peers',
                           'peers': peers})

    # NOTE: reset tabs_html here
    tabs_html = '''
</div>
</div>

<div id="fields-tab">
<p>
You may enter your individual peers in the form fields below and assign a
shared kind and account expiry time for all entries. Just leave the Action
field to <em>Add</em> unless you want to <em>Update</em> or <em>Remove</em>
existing peers. You are free to leave rows empty, but each field in a peer row
MUST be filled for the row to be treated.
</p>
<div class="enter-form form_container">
%(form_prefix_html)s
%(shared_peer_html)s
<input type="hidden" name="peers_format" value="userid" />
<div class="form-row one-col-grid">
    <div class="col-md-12 mb-1 form-cell">
    <label for="action">Action</label>
    <select class="form-control themed-select html-select fill-width" name="action">
        <option value="add">Add</option>
        <option value="update">Update</option>
        <option value="remove">Remove</option>
    </select>
    </div>
</div>
'''

    sorted_countries = list_country_codes(configuration)
    # TODO: switch to JS rows with automagic addition to always keep spare row?
    for index in range(edit_entries):
        # NOTE: we arrange each entry into a field_group_N div with a hidden
        #       user ID collector where the field values are merged on submit
        #       and the actual fields are not passed to the backend.
        tabs_html += '''
<div id="field_group_%s" class="field_group">
    <input class="id-collector" type="hidden" name="peers_content" value="" />
    <div class="form-row five-col-grid">
        ''' % index
        for field in peers_fields:
            title = ' '.join([i.capitalize() for i in field.split('_')])
            placeholder = title
            field_extras = 'type="text"'
            # Lock state field until applicable (JS)
            locked = ""
            cols = "col-md-3 mb-3"
            if field.lower() == 'full_name':
                field_extras = 'minlength=3'
            elif field.lower() == 'organization':
                field_extras = 'minlength=2'
            elif field.lower() == 'email':
                placeholder = "Email at organization"
                field_extras = 'type="email" minlength=5'
                cols = "col-md-2 mb-2"
            elif field.lower() == 'country':
                # NOTE: use country drop-down if available
                title = "Country (ISO 3166)"
                placeholder = "2-Letter country code"
                field_extras = 'minlength=2 maxlength=2'
                cols = "col-md-2 mb-2"
            elif field.lower() == 'state':
                title = "State (if applicable)"
                placeholder = "2-Letter state code"
                field_extras += ' minlength=0 maxlength=2'
                locked = "readonly"
                cols = "col-md-2 mb-2"
            entry_fill = {'field': field, 'title': title, 'placeholder':
                          placeholder, 'extras': field_extras, 'locked':
                          locked, 'cols': cols}
            tabs_html += '''
      <div class="%(cols)s form-cell %(field)s-cell">
          <label for="%(field)s">%(title)s</label><br/>
          ''' % entry_fill
            if field == 'country' and sorted_countries:
                # Generate drop-down of countries and codes if available, else
                # simple input
                tabs_html += '''
        <select class="form-control %(field)s themed-select html-select entry-field fill-with"
          %(extras)s placeholder="%(placeholder)s" %(locked)s onChange="toggle_state();">
''' % entry_fill
                for (name, code) in [('', '')] + sorted_countries:
                    tabs_html += "        <option value='%s'>%s</option>\n" % \
                                 (code, name)
                tabs_html += """
        </select>
    """
            else:
                tabs_html += '''
          <input class="form-control %(field)s entry-field fill-width" %(extras)s
            placeholder="%(placeholder)s" %(locked)s onBlur="toggle_state();" />
            ''' % entry_fill
            tabs_html += '''
      </div>
''' % entry_fill

        tabs_html += '''
    </div>
</div>
'''

    tabs_html += '''
<p>
<span class="switch-label">Invite on email</span>
<label class="switch" for="fields_invite">
<input id="fields_invite" type="checkbox" name="peers_invite">
<span class="slider round small" title="Optional email invitation"></span>
</label>
</p>
%(form_suffix_html)s
</div>
</div>
'''
    tabs_html += '''
<div id="import-tab" >
<p>
You can paste or enter a CSV-formatted list below to create or update your
existing peers. The contents must start with a single header line to define
the field order in the following individual user lines as shown in the example
at the bottom.
</p>
<div class="import-form form_container">
<h2>Import/Update Peers</h2>
%(form_prefix_html)s
%(shared_peer_html)s
<input type="hidden" name="peers_format" value="csvform" />
<input type="hidden" name="action" value="import" />
<textarea class="fillwidth" name="peers_content" rows=10 title="CSV list of peers"
  placeholder="Paste or enter CSV-formatted list of peers ..."></textarea>
<p>
<span class="switch-label">Invite on email</span>
<label class="switch" for="import_invite">
<input id="import_invite" type="checkbox" name="peers_invite">
<span class="slider round small" title="Optional email invitation"></span>
</label>
</p>
%(form_suffix_html)s
</div>
'''
    tabs_html += '''
<br/>
<div class="peers_export init-collapsed accordion invert-theme">
<h4>Example Peers</h4>
<p class="verbatim">%s
%s
...
%s
</p>
</div>
</div>
''' % (fill_helpers['csv_header'],
       csv_sep.join([sample_users[0].get(i, '') for i in peers_fields]),
       csv_sep.join([sample_users[-1].get(i, '') for i in peers_fields]))

    tabs_html += '''
<div id="requests-tab" >
<p>
If someone requests an external user account on %(site)s and explicitly
references you as sponsor or contact person the site admins will generally
forward the request, so that it shows up here for you to confirm. You can then
accept or reject the individual requests below to let the site admins proceed
with account creation or rejection. Please select an expire date to provide
limited but sufficiently long account access - it can always be extended later.
</p>
'''

    pending_count = 0
    for (peer_id, user) in pending_peers:
        # TODO: consider still showing if expired?
        # Skip already accepted request
        if peer_id in all_peers:
            continue
        pending_count += 1
        tabs_html += '''
<div class="requests-form form_container">
%(form_prefix_html)s
%(shared_peer_html)s
<br/>
<input type="hidden" name="peers_format" value="userid" />
<div class="form-row six-col-grid">
    <div class="col-md-2 mb-6 form-cell">
    <label for="action">Action</label>
    <select class="form-control themed-select html-select fill-width" name="action">
        <option value="accept">Accept</option>
        <option value="reject">Reject</option>
    </select>
    </div>
'''
        tabs_html += '''
    <input type="hidden" name="peers_content" value="%(distinguished_name)s" />
''' % user
        for field in peers_fields:
            title = ' '.join([i.capitalize() for i in field.split('_')])
            tabs_html += '''
    <div class="col-md-2 mb-6 form-cell">
        <label for="%(field)s">%(title)s</label>
        <input class="form-control fill-width" type="text" value="%(value)s"
          readonly />
    </div>''' % {'field': field, 'title': title, 'value': user.get(field, '')}
        tabs_html += '''
</div>
%(form_accept_html)s
</div>
'''
    if pending_count < 1:
        tabs_html += '''
<p class="info icon iconpadding">
No pending requests ...
</p>
'''
    tabs_html += '''
</div>
'''

    tabs_html += '''
<div id="urlfetch-tab" >
<p>
In case you have a general project participation list online you can specify the URL here to fetch and parse the list into a peers list. Please note that this memberlist should still be on the machine readbale format described on the upload tab.
</p>
<div class="urlfetch-form form_container">
%(form_prefix_html)s
%(shared_peer_html)s
<br/>
<input type="hidden" name="action" value="import" />
<input type="hidden" name="peers_format" value="csvurl" />
<input class="fillwidth" type="text" name="peers_content" value=""
    placeholder="URL to fetch CSV-formatted list of peers from ..." /><br/>
%(form_suffix_html)s
</div>
</div>
'''

    # End wrap-tabs div
    tabs_html += '''
</div >
'''
    output_objects.append(
        {'object_type': 'html_form', 'text': tabs_html % fill_helpers})

    # Helper form for post

    helper = html_post_helper('peer_action', '%s.py' % target_op,
                              {'action': '__DYNAMIC__',
                               'peers_label': '__DYNAMIC__',
                               'peers_kind': '__DYNAMIC__',
                               'peers_expire': '__DYNAMIC__',
                               'peers_content': '__DYNAMIC__',
                               'peers_invite': '__DYNAMIC__',
                               'peers_format': 'userid',
                               csrf_field: csrf_token})
    output_objects.append({'object_type': 'html_form', 'text':
                           helper})

    logger.info("%s end for %s" % (op_name, client_id))
    return (output_objects, returnvalues.OK)
예제 #16
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)
    action = accepted['action'][-1]
    req_list = accepted['req_id']
    job_list = accepted['job_id']
    lines = int(accepted['lines'][-1])

    meta = '''<meta http-equiv="refresh" content="%s" />
''' % configuration.sleep_secs
    title_entry = find_entry(output_objects, 'title')
    title_entry['text'] = '%s administration panel' % configuration.short_title
    title_entry['meta'] = meta

    # jquery support for tablesorter and confirmation on "remove"
    # table initially sorted by col. 9 (created)

    table_spec = {'table_id': 'accountreqtable', 'sort_order': '[[9,0]]'}
    (add_import, add_init, add_ready) = man_base_js(configuration,
                                                    [table_spec])
    add_ready += '''
            $(".migadmin-tabs").tabs();
'''
    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)
    })

    if not is_admin(client_id, configuration, logger):
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            'You must be an admin to access this control panel.'
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    html = ''
    if action and not action in grid_actions.keys() + accountreq_actions:
        output_objects.append({
            'object_type': 'error_text',
            'text': 'Invalid action: %s' % action
        })
        return (output_objects, returnvalues.SYSTEM_ERROR)
    if action in grid_actions:
        msg = "%s" % grid_actions[action]
        if job_list:
            msg += ' %s' % ' '.join(job_list)
        msg += '\n'
        if not send_message_to_grid_script(msg, logger, configuration):
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                '''Error sending %s message to grid_script.''' % action
            })
            status = returnvalues.SYSTEM_ERROR
    elif action in accountreq_actions:
        if action == "addaccountreq":
            for req_id in req_list:
                if accept_account_req(req_id, configuration):
                    output_objects.append({
                        'object_type':
                        'text',
                        'text':
                        'Accepted account request %s' % req_id
                    })
                else:
                    output_objects.append({
                        'object_type':
                        'error_text',
                        'text':
                        'Accept account request failed - details in log'
                    })
        elif action == "delaccountreq":
            for req_id in req_list:
                if delete_account_req(req_id, configuration):
                    output_objects.append({
                        'object_type':
                        'text',
                        'text':
                        'Deleted account request %s' % req_id
                    })
                else:
                    output_objects.append({
                        'object_type':
                        'error_text',
                        'text':
                        'Delete account request failed - details in log'
                    })

    show, drop = '', ''
    general = """
<h2>Server Status</h2>
<p class='importanttext'>
This page automatically refreshes every %s seconds.
</p>
<p>
You can see the current grid daemon status and server logs below. The buttons
provide access to e.g. managing the grid job queues.
</p>
<form method='get' action='migadmin.py'>
    <input type='hidden' name='action' value='' />
    <input type='submit' value='Show last log lines' />
    <input type='text' size='2' name='lines' value='%s' />
</form>
<br />
<form method='get' action='migadmin.py'>
    <input type='hidden' name='lines' value='%s' />
    <input type='hidden' name='action' value='reloadconfig' />
    <input type='submit' value='Reload Configuration' />
</form>
<br />
""" % (configuration.sleep_secs, lines, lines)
    show += """
<form method='get' action='migadmin.py'>
    <input type='hidden' name='lines' value='%s' />
    <input type='submit' value='Log Jobs' />
    <select name='action'>
""" % lines
    drop += """
<form method='get' action='migadmin.py'>
    <input type='hidden' name='lines' value='%s' />
    <input type='submit' value='Drop Job' />
    <select name='action'>
""" % lines
    for queue in ['queued', 'executing', 'done']:
        selected = ''
        if action.find(queue) != -1:
            selected = 'selected'
        show += "<option %s value='show%s'>%s</option>" % (selected, queue,
                                                           queue)
        drop += "<option %s value='drop%s'>%s</option>" % (selected, queue,
                                                           queue)
    show += """
    </select>
</form>
<br />
"""
    drop += """
    </select>
    <input type='text' size='20' name='job_id' value='' />
</form>
<br />
"""

    html += general
    html += show
    html += drop

    daemons = """
<div id='daemonstatus'>
"""
    daemon_names = []
    if configuration.site_enable_jobs:
        daemon_names += ['grid_script.py', 'grid_monitor.py', 'grid_sshmux.py']
    if configuration.site_enable_events:
        daemon_names.append('grid_events.py')
    # No need to run im_notify unless any im notify protocols are enabled
    if configuration.site_enable_imnotify and \
            [i for i in configuration.notify_protocols if i != 'email']:
        daemon_names.append('grid_imnotify.py')
    if configuration.site_enable_sftp:
        daemon_names.append('grid_sftp.py')
    if configuration.site_enable_davs:
        daemon_names.append('grid_webdavs.py')
    if configuration.site_enable_ftps:
        daemon_names.append('grid_ftps.py')
    if configuration.site_enable_openid:
        daemon_names.append('grid_openid.py')
    if configuration.site_enable_transfers:
        daemon_names.append('grid_transfers.py')
    if configuration.site_enable_crontab:
        daemon_names.append('grid_cron.py')
    if configuration.site_enable_seafile:
        daemon_names += [
            'seafile-controller', 'seaf-server', 'ccnet-server', 'seahub'
        ]
        if configuration.seafile_mount:
            daemon_names.append('seaf-fuse')
    if configuration.site_enable_sftp_subsys:
        daemon_names.append(
            '/sbin/sshd -f /etc/ssh/sshd_config-MiG-sftp-subsys')
    for proc in daemon_names:
        # NOTE: we use command list here to avoid shell requirement
        pgrep_proc = subprocess_popen(['pgrep', '-f', proc],
                                      stdout=subprocess_pipe,
                                      stderr=subprocess_stdout)
        pgrep_proc.wait()
        ps_out = pgrep_proc.stdout.read().strip()
        if pgrep_proc.returncode == 0:
            daemons += "<div class='status_online'>%s running (pid %s)</div>" \
                       % (proc, ps_out)
        else:
            daemons += "<div class='status_offline'>%s not running!</div>" % \
                       proc
    daemons += """</div>
<br />
"""
    html += daemons

    log_path_list = []
    if os.path.isabs(configuration.logfile):
        log_path_list.append(configuration.logfile)
    else:
        log_path_list.append(
            os.path.join(configuration.log_dir, configuration.logfile))
    for log_path in log_path_list:
        html += '''
<h2>%s</h2>
<textarea class="fillwidth padspace" rows=%s readonly="readonly">
''' % (log_path, lines)
        log_lines = read_tail(log_path, lines, logger)
        html += ''.join(log_lines[-lines:])
        html += '''</textarea>
'''

    output_objects.append({
        'object_type':
        'html_form',
        'text':
        """
<div id='wrap-tabs' class='migadmin-tabs'>
<ul>
<li><a href='#serverstatus-tab'>Server Status</a></li>
<li><a href='#accountreqs-tab'>Account Requests</a></li>
<li><a href='#sitestats-tab'>Site Stats</a></li>
</ul>
"""
    })

    output_objects.append({
        'object_type': 'html_form',
        'text': '''
<div id="serverstatus-tab">
'''
    })
    output_objects.append({'object_type': 'html_form', 'text': html})
    output_objects.append({
        'object_type': 'html_form',
        'text': '''
    </div>
'''
    })

    html = ''
    output_objects.append({
        'object_type': 'html_form',
        'text': '''
<div id="accountreqs-tab">
'''
    })
    output_objects.append({
        'object_type': 'header',
        'text': 'Pending Account Requests'
    })

    (list_status, ret) = list_account_reqs(configuration)
    if not list_status:
        logger.error("%s: failed for '%s': %s" % (op_name, client_id, ret))
        output_objects.append({'object_type': 'error_text', 'text': ret})
        return (output_objects, returnvalues.SYSTEM_ERROR)

    form_method = 'post'
    csrf_limit = get_csrf_limit(configuration)
    target_op = 'migadmin'
    csrf_token = make_csrf_token(configuration, form_method, target_op,
                                 client_id, csrf_limit)
    accountreqs = []
    for req_id in ret:
        (load_status, req_dict) = get_account_req(req_id, configuration)
        if not load_status:
            logger.error("%s: load failed for '%s': %s" %
                         (op_name, req_id, req_dict))
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'Could not read details for "%s"' % req_id
            })
            return (output_objects, returnvalues.SYSTEM_ERROR)
        req_item = build_accountreqitem_object(configuration, req_dict)

        js_name = 'create%s' % req_id
        helper = html_post_helper(js_name, '%s.py' % target_op, {
            'action': 'addaccountreq',
            'req_id': req_id,
            csrf_field: csrf_token
        })
        output_objects.append({'object_type': 'html_form', 'text': helper})
        req_item['addaccountreqlink'] = {
            'object_type':
            'link',
            'destination':
            "javascript: confirmDialog(%s, '%s');" %
            (js_name, 'Really accept %s?' % req_id),
            'class':
            'addlink iconspace',
            'title':
            'Accept %s' % req_id,
            'text':
            ''
        }
        js_name = 'delete%s' % req_id
        helper = html_post_helper(js_name, '%s.py' % target_op, {
            'action': 'delaccountreq',
            'req_id': req_id,
            csrf_field: csrf_token
        })
        output_objects.append({'object_type': 'html_form', 'text': helper})
        req_item['delaccountreqlink'] = {
            'object_type':
            'link',
            'destination':
            "javascript: confirmDialog(%s, '%s');" %
            (js_name, 'Really remove %s?' % req_id),
            'class':
            'removelink iconspace',
            'title':
            'Remove %s' % req_id,
            'text':
            ''
        }
        accountreqs.append(req_item)

    output_objects.append({
        'object_type': 'table_pager',
        'entry_name': 'pending certificate/OpenID account requests',
        'default_entries': default_pager_entries
    })
    output_objects.append({
        'object_type': 'accountreqs',
        'accountreqs': accountreqs
    })

    output_objects.append({'object_type': 'html_form', 'text': html})
    output_objects.append({
        'object_type': 'html_form',
        'text': '''
    </div>
'''
    })
    output_objects.append({
        'object_type': 'html_form',
        'text': '''
    <div id="sitestats-tab">
'''
    })
    html = ''
    html += """
<h2>Site Statistics</h2>
"""
    sitestats_home = configuration.sitestats_home
    if sitestats_home and os.path.isdir(sitestats_home):
        html += '''
        <div id=""all-stats">
'''
        all_stats = {}
        # Grab first available format for each stats file
        for filename in listdir(sitestats_home):
            prefix, ext = os.path.splitext(filename)
            file_format = ext.lstrip('.')
            if not file_format in ['pickle', 'json', 'yaml']:
                continue
            path = os.path.join(sitestats_home, filename)
            stats = all_stats[prefix] = all_stats.get(prefix, {})
            if not stats:
                stats = load(path, serializer=file_format)
                all_stats[prefix].update(force_utf8_rec(stats))

        sorted_stats = all_stats.items()
        sorted_stats.sort()
        for (name, stats) in sorted_stats:
            html += format_stats(name, stats)
        html += '''
        </div>
'''

    else:
        html += '<span class="warningtext">Site stats not available</span>'

    output_objects.append({'object_type': 'html_form', 'text': html})
    output_objects.append({
        'object_type': 'html_form',
        'text': '''
    </div>
'''
    })

    # Finish tabs wrap
    output_objects.append({'object_type': 'html_form', 'text': '''
</div>
'''})
    return (output_objects, returnvalues.OK)