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)
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)
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)
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, '')
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)