Esempio n. 1
0
def parse_peers_userid(configuration, raw_entries):
    """Parse list of user IDs into a list of peers"""
    _logger = configuration.logger
    peers = []
    err = []
    for entry in raw_entries:
        raw_user = distinguished_name_to_user(entry.strip())
        missing = [i for i in peers_fields if i not in raw_user]
        if missing:
            err.append("Parsed peers did NOT contain required field(s): %s"
                       % ', '.join(missing))
            continue
        # IMPORTANT: extract ONLY peers fields and validate to avoid abuse
        peer_user = dict([(i, raw_user.get(i, '').strip())
                          for i in peers_fields])
        defaults = dict([(i, REJECT_UNSET) for i in peer_user])
        (accepted, rejected) = validated_input(peer_user, defaults,
                                               list_wrap=True)
        if rejected:
            _logger.warning('skip peer with invalid value(s): %s : %s'
                            % (entry, rejected))
            unsafe_err = ' , '.join(
                ['%s=%r' % pair for pair in peer_user.items()])
            unsafe_err += '. Rejected values: ' + ', '.join(rejected)
            err.append("Skip peer user with invalid value(s): %s" %
                       html_escape(unsafe_err))
            continue
        peers.append(canonical_user(configuration, peer_user, peers_fields))
    _logger.debug('parsed user id into peers: %s' % peers)
    return (peers, err)
Esempio n. 2
0
def validate_input(user_arguments_dict,
                   defaults,
                   output_objects,
                   allow_rejects,
                   prefilter_map=None,
                   typecheck_overrides={}):
    """A wrapper used by most back end functionality.
    The optional typecheck_overrides argument can be passed a dictionary of
    input variable names and their validator function if needed. This is
    particularly useful in relation to overriding the default simple path value
    checks in cases where a path pattern with wildcards is allowed.
    We want all such exceptions to be explicit to avoid opening up by mistake.
    """

    # always allow output_format, csrf_field, stray modauthopenid nonces and
    # underscore cache-prevention dummy - we don't want redundant lines in all
    # scripts for that.
    # NOTE: use AllowMe to avoid input validation errors from e.g. underscore

    defaults['output_format'] = ['AllowMe']
    defaults[csrf_field] = ['AllowMe']
    # This sometimes appears from modauthopenid and would otherwise cause e.g.
    # input parsing error: modauthopenid.nonce: YDHZzoBtgI: unexpected field
    defaults['modauthopenid.nonce'] = ['AllowMe']
    defaults['_'] = ['AllowMe']
    if prefilter_map:
        prefilter_input(user_arguments_dict, prefilter_map)
    (accepted, rejected) = validated_input(user_arguments_dict,
                                           defaults,
                                           type_override=typecheck_overrides)
    warn_on_rejects(rejected, output_objects)
    if rejected.keys() and not allow_rejects:
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            'Input arguments were rejected - not allowed for this script!'
        })
        output_objects.append({
            'object_type': 'link',
            'text': 'Go back to try again',
            'destination': 'javascript:history.back();',
        })
        return (False, output_objects)
    return (True, accepted)
Esempio n. 3
0
def parse_peers_form(configuration, raw_lines, csv_sep):
    """Parse CSV form of peers into a list of peers"""
    _logger = configuration.logger
    header = None
    peers = []
    err = []
    for line in raw_lines.split('\n'):
        line = line.split('#', 1)[0].strip()
        if not line:
            continue
        parts = [i.strip() for i in line.split(csv_sep)]
        if not header:
            missing = [i for i in peers_fields if i not in parts]
            if missing:
                err.append("Parsed peers did NOT contain required field(s): %s"
                           % ', '.join(missing))
            header = parts
            continue
        if len(header) != len(parts):
            _logger.warning('skip peers line with mismatch in field count: %s'
                            % line)
            err.append("Skip peers line not matching header format: %s" %
                       html_escape(line + ' vs ' + csv_sep.join(header)))
            continue
        raw_user = dict(zip(header, parts))
        # IMPORTANT: extract ONLY peers fields and validate to avoid abuse
        peer_user = dict([(i, raw_user.get(i, '')) for i in peers_fields])
        defaults = dict([(i, REJECT_UNSET) for i in peer_user])
        (accepted, rejected) = validated_input(peer_user, defaults,
                                               list_wrap=True)
        if rejected:
            _logger.warning('skip peer with invalid value(s): %s (%s)'
                            % (line, rejected))
            unsafe_err = ' , '.join(
                ['%s=%r' % pair for pair in peer_user.items()])
            unsafe_err += '. Rejected values: ' + ', '.join(rejected)
            err.append("Skip peer user with invalid value(s): %s" %
                       html_escape(unsafe_err))
            continue
        peers.append(canonical_user(configuration, peer_user, peers_fields))
    _logger.debug('parsed form into peers: %s' % peers)
    return (peers, err)
Esempio n. 4
0
def valid_attributes_for_type(configuration, attributes, request_type):
    if request_type not in VALID_REQUEST_ATTRIBUTES_SIGNATURE:
        msg = 'Request type %s has no supported attributes.' % request_type
        configuration.logger.warning(msg)
        return (False, msg)

    if request_type not in VALID_REQUEST_ATTRIBUTES_TYPE:
        msg = 'Request type %s has no supported attributes.' % request_type
        configuration.logger.warning(msg)
        return (False, msg)

    signature = VALID_REQUEST_ATTRIBUTES_SIGNATURE[request_type]
    type_map = VALID_REQUEST_ATTRIBUTES_TYPE[request_type]
    accepted, rejected = validated_input(attributes,
                                         signature,
                                         type_override=type_map,
                                         list_wrap=True)

    if not accepted or rejected:
        msg = "Invalid input was supplied to the requests API: %s" % rejected
        return (False, msg)

    return (True, '')
Esempio n. 5
0
def main(client_id, user_arguments_dict):
    """
    Main function used by front end.
    :param client_id: A MiG user.
    :param user_arguments_dict: A JSON message sent to the MiG. This will be
    parsed and if valid, the relevant API handler functions are called to
    generate meaningful output.
    :return: (Tuple (list, Tuple(integer,string))) Returns a tuple with the
    first value being a list of output objects generated by the call. The
    second value is also a tuple used for error code reporting, with the first
    value being an error code and the second being a brief explanation.
    """
    # Ensure that the output format is in JSON
    user_arguments_dict['output_format'] = ['json']
    user_arguments_dict.pop('__DELAYED_INPUT__', None)
    (configuration, logger, output_objects, op_name) = \
        initialize_main_variables(client_id, op_title=False, op_header=False,
                                  op_menu=False)

    # Add allow Access-Control-Allow-Origin to headers
    # Required to allow Jupyter Widget from localhost to request against the
    # API
    # TODO, possibly restrict allowed origins
    output_objects[0]['headers'].append(('Access-Control-Allow-Origin', '*'))
    output_objects[0]['headers'].append(
        ('Access-Control-Allow-Headers', 'Content-Type'))
    output_objects[0]['headers'].append(('Access-Control-Max-Age', 600))
    output_objects[0]['headers'].append(
        ('Access-Control-Allow-Methods', 'POST, OPTIONS'))
    output_objects[0]['headers'].append(('Content-Type', 'application/json'))

    if not correct_handler('POST'):
        msg = "Interaction from %s not POST request" % client_id
        logger.error(msg)
        output_objects.append({
            'object_type': 'error_text',
            'text': html_escape(msg)
        })
        return (output_objects, returnvalues.SYSTEM_ERROR)

    if not configuration.site_enable_workflows:
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            'Workflows are not enabled on this system'
        })
        return (output_objects, returnvalues.SYSTEM_ERROR)

    # Input data
    data = sys.stdin.read()
    try:
        json_data = json.loads(data, object_hook=force_utf8_rec)
    except ValueError:
        msg = "An invalid format was supplied to: '%s', requires a JSON " \
              "compatible format" % op_name
        logger.error(msg)
        output_objects.append({
            'object_type': 'error_text',
            'text': html_escape(msg)
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    # IMPORTANT!! Do not access the json_data input before it has been
    # validated by validated_input. Note attributes entry has not yet been
    # validated, this is done once the type and operation is determined
    accepted, rejected = validated_input(json_data,
                                         REQUEST_SIGNATURE,
                                         type_override=REQUEST_TYPE_MAP,
                                         value_override=REQUEST_VALUE_MAP,
                                         list_wrap=True)

    if not accepted or rejected:
        logger.error("A validation error occurred: '%s'" % rejected)
        msg = "Invalid input was supplied to the request API: %s" % rejected
        # TODO, Transform error messages to something more readable
        output_objects.append({
            'object_type': 'error_text',
            'text': html_escape(msg)
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    request_type = accepted.pop('type', [None])[0]
    operation = accepted.pop('operation', None)[0]
    workflow_session_id = accepted.pop('workflowsessionid', None)[0]
    vgrid = accepted.pop('vgrid', None)
    # Note these have not been sufficiently checked, and should not be accessed
    # until the valid_attributes_for_type function has been called on them.
    # This is done later once we have checked the operation and request_type.
    attributes = {}
    for key, value in accepted.items():
        if key in json_data['attributes']:
            attributes[key] = value
    attributes['vgrid'] = vgrid

    if not valid_session_id(configuration, workflow_session_id):
        output_objects.append({
            'object_type': 'error_text',
            'text': 'Invalid workflowsessionid'
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    # workflow_session_id symlink points to the vGrid it gives access to
    workflow_sessions_db = []
    try:
        workflow_sessions_db = load_workflow_sessions_db(configuration)
    except IOError:
        logger.info("Workflow sessions db didn't load, creating new db")
        if not touch_workflow_sessions_db(configuration, force=True):
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                "Internal sessions db failure, please contact "
                "an admin at '%s' to resolve this issue." %
                configuration.admin_email
            })
            return (output_objects, returnvalues.SYSTEM_ERROR)
        else:
            # Try reload
            workflow_sessions_db = load_workflow_sessions_db(configuration)

    if workflow_session_id not in workflow_sessions_db:
        logger.error("Workflow session '%s' from user '%s' not found in "
                     "database" % (workflow_session_id, client_id))
        configuration.auth_logger.error(
            "Workflow session '%s' provided by user '%s' but not present in "
            "database" % (workflow_session_id, client_id))
        # TODO Also track multiple attempts from the same IP
        output_objects.append({
            'object_type': 'error_text',
            'text': 'Invalid workflowsessionid'
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    workflow_session = workflow_sessions_db.get(workflow_session_id)
    logger.info('jsoninterface found %s' % workflow_session)
    owner = workflow_session['owner']

    # User is vgrid owner or member
    success, msg, _ = init_vgrid_script_list(vgrid, owner, configuration)
    if not success:
        logger.error("Illegal access attempt by user '%s' to vgrid '%s'. %s" %
                     (owner, vgrid, msg))
        output_objects.append({
            'object_type': 'error_text',
            'text': html_escape(msg)
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    status, msg = valid_attributes_for_type(configuration, attributes,
                                            request_type)
    if not status:
        output_objects.append({
            'object_type': 'error_text',
            'text': html_escape(msg)
        })
        return (output_objects, returnvalues.SYSTEM_ERROR)

    status, msg = valid_operation_for_type(configuration, operation,
                                           request_type)
    if not status:
        output_objects.append({
            'object_type': 'error_text',
            'text': html_escape(msg)
        })
        return (output_objects, returnvalues.SYSTEM_ERROR)

    request_func = VALID_REQUEST_OPERATIONS[request_type][operation]
    status, response = request_func(configuration, owner, attributes)

    output_objects.append(response)
    if not status:
        return (output_objects, returnvalues.SYSTEM_ERROR)

    return (output_objects, returnvalues.OK)