def _set_rpsl_object_schemas(self): """ Create the schemas for each specific RPSL object class. Each of these implements RPSLObject, and RPSLPerson/RPSLRole implement RPSLContact as well. """ self.graphql_types = defaultdict(dict) schemas = OrderedDict() for object_class, klass in OBJECT_CLASS_MAPPING.items(): object_name = klass.__name__ graphql_fields = OrderedDict() graphql_fields['rpslPk'] = 'String' graphql_fields['objectClass'] = 'String' graphql_fields['objectText'] = 'String' graphql_fields['updated'] = 'String' graphql_fields['journal'] = '[RPSLJournalEntry]' for field_name, field in klass.fields.items(): graphql_type = self._graphql_type_for_rpsl_field(field) graphql_fields[snake_to_camel_case(field_name)] = graphql_type self.graphql_types[snake_to_camel_case( object_name)][field_name] = graphql_type reference_name, reference_type = self._grapql_type_for_reference_field( field_name, field) if reference_name and reference_type: graphql_fields[reference_name] = reference_type self.graphql_types[object_name][ reference_name] = reference_type for field_name in klass.field_extracts: if field_name.startswith('asn'): graphql_type = 'ASN' elif field_name == 'prefix': graphql_type = 'IP' elif field_name == 'prefix_length': graphql_type = 'Int' else: graphql_type = 'String' graphql_fields[snake_to_camel_case(field_name)] = graphql_type if klass.rpki_relevant: graphql_fields['rpkiStatus'] = 'RPKIStatus' graphql_fields['rpkiMaxLength'] = 'Int' self.graphql_types[object_name]['rpki_max_length'] = 'Int' implements = 'RPSLContact & RPSLObject' if klass in [ RPSLPerson, RPSLRole ] else 'RPSLObject' schema = self._generate_schema_str(object_name, 'type', graphql_fields, implements) schemas[object_name] = schema self.rpsl_object_schemas = schemas
def _grapql_type_for_reference_field( self, field_name: str, rpsl_field: RPSLTextField) -> Tuple[Optional[str], Optional[str]]: """ Return the GraphQL name and type for a reference field. For example, for a field "admin-c" that refers to person/role, returns ('adminC', '[RPSLContactUnion!]'). Some fields are excluded because they are syntactical references, not real references. """ if isinstance(rpsl_field, RPSLReferenceField) and getattr( rpsl_field, 'referring', None): rpsl_field.resolve_references() graphql_name = snake_to_camel_case(field_name) + 'Objs' grapql_referring = set(rpsl_field.referring_object_classes) if RPSLAutNum in grapql_referring: grapql_referring.remove(RPSLAutNum) if RPSLInetRtr in grapql_referring: grapql_referring.remove(RPSLInetRtr) if grapql_referring == {RPSLPerson, RPSLRole}: graphql_type = '[RPSLContactUnion!]' else: graphql_type = '[' + grapql_referring.pop().__name__ + '!]' return graphql_name, graphql_type return None, None
def _set_rpsl_query_fields(self): """ Create a sub-schema for the fields that can be queried for RPSL objects. This includes all fields from all objects, along with a few special fields. """ string_list_fields = {'rpsl_pk', 'sources', 'object_class'}.union(lookup_field_names()) params = [ snake_to_camel_case(p) + ': [String!]' for p in sorted(string_list_fields) ] params += [ 'ipExact: IP', 'ipLessSpecific: IP', 'ipLessSpecificOneLevel: IP', 'ipMoreSpecific: IP', 'ipAny: IP', 'asn: [ASN!]', 'rpkiStatus: [RPKIStatus!]', 'scopeFilterStatus: [ScopeFilterStatus!]', 'textSearch: String', 'recordLimit: Int', 'sqlTrace: Boolean', ] self.rpsl_query_fields = ', '.join(params)
def _rpsl_db_query_to_graphql_out(query: RPSLDatabaseQuery, info: GraphQLResolveInfo): """ Given an RPSL database query, execute it and clean up the output to be suitable to return to GraphQL. Main changes are: - Enum handling - Adding the asn and prefix fields if applicable - Ensuring the right fields are returned as a list of strings or a string """ database_handler = info.context['request'].app.state.database_handler if info.context.get('sql_trace'): if 'sql_queries' not in info.context: info.context['sql_queries'] = [repr(query)] else: info.context['sql_queries'].append(repr(query)) for row in database_handler.execute_query(query, refresh_on_error=True): graphql_result = { snake_to_camel_case(k): v for k, v in row.items() if k != 'parsed_data' } if 'object_text' in row: graphql_result['objectText'] = remove_auth_hashes( row['object_text']) if 'rpki_status' in row: graphql_result['rpkiStatus'] = row['rpki_status'] if row.get('ip_first') is not None and row.get('prefix_length'): graphql_result['prefix'] = row['ip_first'] + '/' + str( row['prefix_length']) if row.get('asn_first') is not None and row.get( 'asn_first') == row.get('asn_last'): graphql_result['asn'] = row['asn_first'] object_type = resolve_rpsl_object_type(row) for key, value in row.get('parsed_data', dict()).items(): if key == 'auth': value = [remove_auth_hashes(v) for v in value] graphql_type = schema.graphql_types[object_type][key] if graphql_type == 'String' and isinstance(value, list): value = '\n'.join(value) graphql_result[snake_to_camel_case(key)] = value yield graphql_result
def resolve_database_status(_, info: GraphQLResolveInfo, sources: Optional[List[str]] = None): """Resolve a databaseStatus query""" query_resolver = QueryResolver( info.context['request'].app.state.preloader, info.context['request'].app.state.database_handler) for name, data in query_resolver.database_status(sources=sources).items(): camel_case_data = OrderedDict(data) camel_case_data['source'] = name for key, value in data.items(): camel_case_data[snake_to_camel_case(key)] = value yield camel_case_data
def _dict_for_common_fields(self, common_fields: List[str]): common_field_dict = OrderedDict() for field_name in sorted(common_fields): try: # These fields are present in all relevant object, so this is a safe check rpsl_field = RPSLPerson.fields[field_name] graphql_type = self._graphql_type_for_rpsl_field(rpsl_field) reference_name, reference_type = self._grapql_type_for_reference_field( field_name, rpsl_field) if reference_name and reference_type: common_field_dict[reference_name] = reference_type except KeyError: graphql_type = 'String' common_field_dict[snake_to_camel_case(field_name)] = graphql_type return common_field_dict
def resolve_rpsl_object_journal(rpsl_object, info: GraphQLResolveInfo): """ Resolve a journal subquery on an RPSL object. """ database_handler = info.context['request'].app.state.database_handler access_list = f"sources.{rpsl_object['source']}.nrtm_access_list" if not is_client_permitted(info.context['request'].client.host, access_list): raise GraphQLError( f"Access to journal denied for source {rpsl_object['source']}") query = RPSLDatabaseJournalQuery() query.sources([rpsl_object['source']]).rpsl_pk(rpsl_object['rpslPk']) for row in database_handler.execute_query(query, refresh_on_error=True): response = {snake_to_camel_case(k): v for k, v in row.items()} response['operation'] = response['operation'].name if response['origin']: response['origin'] = response['origin'].name if response['objectText']: response['objectText'] = remove_auth_hashes(response['objectText']) yield response