def get(self):
        if self.config.read_auth_key_required and not (self.auth and self.auth.read_permission):
            self.info(403, message="Missing or invalid authorization key", style="plain")
            return

        pfif_version = self.params.version

        # Note that self.request.get can handle multiple IDs at once; we
        # can consider adding support for multiple records later.
        record_id = self.request.get("id")
        if not record_id:
            self.info(400, message="Missing id parameter", style="plain")
            return

        person = model.Person.get(self.repo, record_id, filter_expired=False)
        if not person:
            self.info(400, message="No person record with ID %s" % record_id, style="plain")
            return
        notes = model.Note.get_by_person_record_id(self.repo, record_id)
        notes = [note for note in notes if not note.hidden]

        self.response.headers["Content-Type"] = "application/xml"
        records = [pfif_version.person_to_dict(person, person.is_expired)]
        note_records = map(pfif_version.note_to_dict, notes)
        utils.optionally_filter_sensitive_fields(records, self.auth)
        utils.optionally_filter_sensitive_fields(note_records, self.auth)
        pfif_version.write_file(self.response.out, records, lambda p: note_records)
        utils.log_api_action(self, ApiActionLog.READ, len(records), len(notes))
Exemple #2
0
    def get(self):
        if self.config.search_auth_key_required and not (
                self.auth and self.auth.search_permission):
            self.info(403,
                      message='Missing or invalid authorization key',
                      style='plain')
            return

        pfif_version = self.params.version

        # Retrieve parameters and do some sanity checks on them.
        record_id = self.request.get('id')
        query_string = self.request.get('q')
        max_results = min(self.params.max_results or 100, HARD_MAX_RESULTS)

        results = []
        if record_id:
            # Search by record ID (always returns just 1 result or nothing).
            person = model.Person.get(self.repo, record_id)
            if person:
                results = [person]
        elif query_string:
            # Search by query words.
            if self.config.external_search_backends:
                query = TextQuery(query_string)
                results = external_search.search(
                    self.repo, query, max_results,
                    self.config.external_search_backends)
            # External search backends are not always complete. Fall back to
            # the original search when they fail or return no results.
            if not results:
                if config.get('enable_fulltext_search'):
                    results = full_text_search.search(self.repo, query_string,
                                                      max_results)
                else:
                    results = indexing.search(self.repo,
                                              TextQuery(query_string),
                                              max_results)
        else:
            self.info(400,
                      message='Neither id nor q parameter specified',
                      style='plain')

        records = [pfif_version.person_to_dict(result) for result in results]
        utils.optionally_filter_sensitive_fields(records, self.auth)

        # Define the function to retrieve notes for a person.
        def get_notes_for_person(person):
            notes = model.Note.get_by_person_record_id(
                self.repo, person['person_record_id'])
            notes = [note for note in notes if not note.hidden]
            records = map(pfif_version.note_to_dict, notes)
            utils.optionally_filter_sensitive_fields(records, self.auth)
            return records

        self.response.headers[
            'Content-Type'] = 'application/xml; charset=utf-8'
        pfif_version.write_file(self.response.out, records,
                                get_notes_for_person)
        utils.log_api_action(self, ApiActionLog.SEARCH, len(records))
Exemple #3
0
    def get(self):
        if self.config.read_auth_key_required and not (
                self.auth and self.auth.read_permission):
            self.info(403,
                      message='Missing or invalid authorization key',
                      style='plain')
            return

        pfif_version = self.params.version

        # Note that self.request.get can handle multiple IDs at once; we
        # can consider adding support for multiple records later.
        record_id = self.request.get('id')
        if not record_id:
            self.info(400, message='Missing id parameter', style='plain')
            return

        person = model.Person.get(self.repo, record_id, filter_expired=False)
        if not person:
            self.info(400,
                      message='No person record with ID %s' % record_id,
                      style='plain')
            return
        notes = model.Note.get_by_person_record_id(self.repo, record_id)
        notes = [note for note in notes if not note.hidden]

        self.response.headers['Content-Type'] = 'application/xml'
        records = [pfif_version.person_to_dict(person, person.is_expired)]
        note_records = map(pfif_version.note_to_dict, notes)
        utils.optionally_filter_sensitive_fields(records, self.auth)
        utils.optionally_filter_sensitive_fields(note_records, self.auth)
        pfif_version.write_file(self.response.out, records,
                                lambda p: note_records)
        utils.log_api_action(self, ApiActionLog.READ, len(records), len(notes))
Exemple #4
0
    def get(self):
        if self.config.read_auth_key_required and not (
            self.auth and self.auth.read_permission):
            self.response.set_status(403)
            self.write('Missing or invalid authorization key\n')
            return

        pfif_version = self.params.version

        # Note that self.request.get can handle multiple IDs at once; we
        # can consider adding support for multiple records later.
        record_id = self.request.get('id')
        if not record_id:
            return self.error(400, 'Missing id parameter')

        person = model.Person.get(
            self.repo, record_id, filter_expired=False)
        if not person:
            return self.error(404, 'No person record with ID %s' % record_id)
        notes = model.Note.get_by_person_record_id(self.repo, record_id)
        notes = [note for note in notes if not note.hidden]

        self.response.headers['Content-Type'] = 'application/xml'
        records = [pfif_version.person_to_dict(person, person.is_expired)]
        note_records = map(pfif_version.note_to_dict, notes)
        utils.optionally_filter_sensitive_fields(records, self.auth)
        utils.optionally_filter_sensitive_fields(note_records, self.auth)
        pfif_version.write_file(
            self.response.out, records, lambda p: note_records)
        utils.log_api_action(
            self, ApiActionLog.READ, len(records), len(notes))
Exemple #5
0
    def get(self):
        if self.config.search_auth_key_required and not (
            self.auth and self.auth.search_permission):
            self.info(
                403,
                message='Missing or invalid authorization key',
                style='plain')
            return

        pfif_version = self.params.version

        # Retrieve parameters and do some sanity checks on them.
        record_id = self.request.get('id')
        query_string = self.request.get('q')
        max_results = min(self.params.max_results or 100, HARD_MAX_RESULTS)

        results = []
        if record_id:
            # Search by record ID (always returns just 1 result or nothing).
            person = model.Person.get(self.repo, record_id)
            if person:
                results = [person]
        elif query_string:
            # Search by query words.
            if self.config.external_search_backends:
                query = TextQuery(query_string)
                results = external_search.search(self.repo, query, max_results,
                    self.config.external_search_backends)
            # External search backends are not always complete. Fall back to
            # the original search when they fail or return no results.
            if not results:
                if config.get('enable_fulltext_search'):
                    results = full_text_search.search(
                        self.repo, query_string, max_results)
                else:
                    results = indexing.search(
                        self.repo, TextQuery(query_string), max_results)
        else:
            self.info(
                400,
                message='Neither id nor q parameter specified',
                style='plain')

        records = [pfif_version.person_to_dict(result) for result in results]
        utils.optionally_filter_sensitive_fields(records, self.auth)

        # Define the function to retrieve notes for a person.
        def get_notes_for_person(person):
            notes = model.Note.get_by_person_record_id(
                self.repo, person['person_record_id'])
            notes = [note for note in notes if not note.hidden]
            records = map(pfif_version.note_to_dict, notes)
            utils.optionally_filter_sensitive_fields(records, self.auth)
            return records

        self.response.headers['Content-Type'] = 'application/xml; charset=utf-8'
        pfif_version.write_file(
            self.response.out, records, get_notes_for_person)
        utils.log_api_action(self, ApiActionLog.SEARCH, len(records))
Exemple #6
0
    def get(self):
        repos = model.Repo.list_launched()
        if self.repo:
            repos = [self.repo] if self.repo in repos else []

        self.response.headers['Content-Type'] = 'application/xml'
        atom.REPO_1_0.write_feed(
            self.response.out, repos, self.request.url, self.TITLE,
            get_latest_repo_updated_date(repos))
        utils.log_api_action(self, model.ApiActionLog.REPO)
Exemple #7
0
    def get(self):
        repos = model.Repo.list_launched()
        if self.repo:
            repos = [self.repo] if self.repo in repos else []

        self.response.headers['Content-Type'] = 'application/xml'
        atom.REPO_1_0.write_feed(self.response.out, repos,
                                 self.request.url, self.TITLE,
                                 get_latest_repo_updated_date(repos))
        utils.log_api_action(self, model.ApiActionLog.REPO)
Exemple #8
0
    def get(self):
        if self.config.read_auth_key_required and not (
                self.auth and self.auth.read_permission):
            self.response.set_status(403)
            self.write('Missing or invalid authorization key\n')
            return

        pfif_version = self.params.version
        atom_version = atom.ATOM_PFIF_VERSIONS.get(pfif_version.version)

        max_results = min(self.params.max_results or 10, HARD_MAX_RESULTS)
        skip = self.params.skip or 0

        # We use a member because a var can't be modified inside the closure.
        self.num_notes = 0

        def get_notes_for_person(person):
            notes = model.Note.get_by_person_record_id(
                self.repo, person['person_record_id'])
            # Show hidden notes as blank in the Person feed (melwitt)
            # https://web.archive.org/web/20111228161607/http://code.google.com/p/googlepersonfinder/issues/detail?id=58
            make_hidden_notes_blank(notes)

            records = map(pfif_version.note_to_dict, notes)
            utils.optionally_filter_sensitive_fields(records, self.auth)
            self.num_notes += len(notes)
            return records

        if self.params.omit_notes:  # Return only the person records.
            get_notes_for_person = lambda person: []

        query = model.Person.all_in_repo(self.repo, filter_expired=False)
        if self.params.min_entry_date:  # Scan forward.
            query = query.order('entry_date')
            query = query.filter('entry_date >=', self.params.min_entry_date)
        else:  # Show recent entries, scanning backward.
            query = query.order('-entry_date')

        persons = query.fetch(max_results, offset=skip)
        updated = get_latest_entry_date(persons)

        self.response.headers[
            'Content-Type'] = 'application/xml; charset=utf-8'
        records = [
            pfif_version.person_to_dict(person, person.is_expired)
            for person in persons
        ]
        utils.optionally_filter_sensitive_fields(records, self.auth)
        atom_version.write_person_feed(self.response.out, records,
                                       get_notes_for_person, self.request.url,
                                       self.env.netloc,
                                       PERSON_SUBTITLE_BASE + self.env.netloc,
                                       updated)
        utils.log_api_action(self, model.ApiActionLog.READ, len(records),
                             self.num_notes)
    def post(self):
        if not (self.auth and self.auth.subscribe_permission):
            return self.error(403, "Missing or invalid authorization key")

        subscription = model.Subscription.get(self.repo, self.params.id, self.params.subscribe_email)
        self.response.set_status(200)
        utils.log_api_action(self, ApiActionLog.UNSUBSCRIBE)
        if subscription:
            subscription.delete()
            return self.info(200, "Successfully unsubscribed")
        return self.info(200, "Not subscribed")
Exemple #10
0
    def post(self):
        if not (self.auth and self.auth.subscribe_permission):
            return self.error(403, 'Missing or invalid authorization key')

        subscription = model.Subscription.get(self.repo, self.params.id,
                                              self.params.subscribe_email)
        self.response.set_status(200)
        utils.log_api_action(self, ApiActionLog.UNSUBSCRIBE)
        if subscription:
            subscription.delete()
            return self.info(200, 'Successfully unsubscribed')
        return self.info(200, 'Not subscribed')
Exemple #11
0
    def post(self):
        if not (self.auth and self.auth.domain_write_permission):
            self.response.set_status(403)
            # TODO(ryok): i18n
            self.write('Missing or invalid authorization key.')
            return

        content = self.request.get('content')
        if not content:
            self.response.set_status(400)
            self.write('You need to specify at least one CSV file.')
            return

        # TODO(ryok): let the user select timezone.
        # TODO(ryok): accept more flexible date time format.
        # TODO(ryok): support non-UTF8 encodings.

        source_domain = self.auth.domain_write_permission
        records = importer.utf8_decoder(
                csv.DictReader(StringIO.StringIO(content)))
        try:
            records = [complete_record_ids(r, source_domain) for r in records]
        except csv.Error:
            self.response.set_status(400)
            self.write('The CSV file is formatted incorrectly.')
            return

        is_empty = lambda x: (x or '').strip()
        persons = [r for r in records if is_empty(r.get('full_name'))]
        notes = [r for r in records if is_empty(r.get('note_record_id'))]

        people_written, people_skipped, people_total = importer.import_records(
            self.repo, source_domain, importer.create_person, persons)
        notes_written, notes_skipped, notes_total = importer.import_records(
            self.repo, source_domain, importer.create_note, notes)

        utils.log_api_action(self, ApiActionLog.WRITE,
                             people_written, notes_written,
                             len(people_skipped), len(notes_skipped))

        self.render('import.html',
                    stats=[
                        Struct(type='Person',
                               written=people_written,
                               skipped=people_skipped,
                               total=people_total),
                        Struct(type='Note',
                               written=notes_written,
                               skipped=notes_skipped,
                               total=notes_total)],
                    **get_tag_params(self))
Exemple #12
0
    def get(self):
        if self.config.read_auth_key_required and not (
            self.auth and self.auth.read_permission):
            self.response.set_status(403)
            self.write('Missing or invalid authorization key\n')
            return

        pfif_version = self.params.version
        atom_version = atom.ATOM_PFIF_VERSIONS.get(pfif_version.version)

        max_results = min(self.params.max_results or 10, HARD_MAX_RESULTS)
        skip = self.params.skip or 0

        # We use a member because a var can't be modified inside the closure.
        self.num_notes = 0
        def get_notes_for_person(person):
            notes = model.Note.get_by_person_record_id(
                self.repo, person['person_record_id'])
            # Show hidden notes as blank in the Person feed (melwitt)
            # http://code.google.com/p/googlepersonfinder/issues/detail?id=58
            make_hidden_notes_blank(notes)

            records = map(pfif_version.note_to_dict, notes)
            utils.optionally_filter_sensitive_fields(records, self.auth)
            self.num_notes += len(notes)
            return records

        if self.params.omit_notes:  # Return only the person records.
            get_notes_for_person = lambda person: []

        query = model.Person.all_in_repo(self.repo, filter_expired=False)
        if self.params.min_entry_date:  # Scan forward.
            query = query.order('entry_date')
            query = query.filter('entry_date >=', self.params.min_entry_date)
        else:  # Show recent entries, scanning backward.
            query = query.order('-entry_date')

        persons = query.fetch(max_results, offset=skip)
        updated = get_latest_entry_date(persons)

        self.response.headers['Content-Type'] = 'application/xml'
        records = [pfif_version.person_to_dict(person, person.is_expired)
                   for person in persons]
        utils.optionally_filter_sensitive_fields(records, self.auth)
        atom_version.write_person_feed(
            self.response.out, records, get_notes_for_person,
            self.request.url, self.env.netloc, PERSON_SUBTITLE_BASE +
            self.env.netloc, updated)
        utils.log_api_action(self, model.ApiActionLog.READ, len(records),
                             self.num_notes)
Exemple #13
0
    def get(self):
        # SSL and auth key is not required if a feed for a specific person
        # is requested. Note that the feed icon on the person record page
        # links to HTTP version without auth key.
        if not self.params.person_record_id:
            # Check for SSL (unless running on localhost for development).
            if self.env.scheme != 'https' and self.env.domain != 'localhost':
                self.response.set_status(403)
                self.write('HTTPS is required.\n')
                return
            if self.config.read_auth_key_required and not (
                    self.auth and self.auth.read_permission):
                self.response.set_status(403)
                self.write('Missing or invalid authorization key\n')
                return

        pfif_version = self.params.version
        atom_version = atom.ATOM_PFIF_VERSIONS.get(pfif_version.version)
        max_results = min(self.params.max_results or 10, HARD_MAX_RESULTS)
        skip = self.params.skip or 0

        query = model.Note.all_in_repo(self.repo)
        if self.params.min_entry_date:  # Scan forward.
            query = query.order('entry_date')
            query = query.filter('entry_date >=', self.params.min_entry_date)
        else:  # Show recent entries, scanning backward.
            query = query.order('-entry_date')

        if self.params.person_record_id:  # Show notes for a specific person.
            query = query.filter('person_record_id =',
                                 self.params.person_record_id)

        notes = query.fetch(max_results, offset=skip)
        updated = get_latest_entry_date(notes)

        # Show hidden notes as blank in the Note feed (melwitt)
        # https://web.archive.org/web/20111228161607/http://code.google.com/p/googlepersonfinder/issues/detail?id=58
        make_hidden_notes_blank(notes)

        self.response.headers[
            'Content-Type'] = 'application/xml; charset=utf-8'
        records = map(pfif_version.note_to_dict, notes)
        utils.optionally_filter_sensitive_fields(records, self.auth)
        atom_version.write_note_feed(self.response.out, records,
                                     self.request.url, self.env.netloc,
                                     NOTE_SUBTITLE_BASE + self.env.netloc,
                                     updated)
        utils.log_api_action(self, model.ApiActionLog.READ, 0, len(records))
    def post(self):
        if not (self.auth and self.auth.subscribe_permission):
            return self.error(403, "Missing or invalid authorization key")

        if not subscribe.is_email_valid(self.params.subscribe_email):
            return self.error(400, "Invalid email address")

        person = model.Person.get(self.repo, self.params.id)
        if not person:
            return self.error(400, "Invalid person_record_id")

        subscription = subscribe.subscribe_to(self, self.repo, person, self.params.subscribe_email, self.env.lang)
        utils.log_api_action(self, ApiActionLog.SUBSCRIBE)
        if not subscription:
            return self.info(200, "Already subscribed")
        return self.info(200, "Successfully subscribed")
Exemple #15
0
    def get(self):
        # SSL and auth key is not required if a feed for a specific person
        # is requested. Note that the feed icon on the person record page
        # links to HTTP version without auth key.
        if not self.params.person_record_id:
          # Check for SSL (unless running on localhost for development).
          if self.env.scheme != 'https' and self.env.domain != 'localhost':
              self.response.set_status(403)
              self.write('HTTPS is required.\n')
              return
          if self.config.read_auth_key_required and not (
              self.auth and self.auth.read_permission):
              self.response.set_status(403)
              self.write('Missing or invalid authorization key\n')
              return

        pfif_version = self.params.version
        atom_version = atom.ATOM_PFIF_VERSIONS.get(pfif_version.version)
        max_results = min(self.params.max_results or 10, HARD_MAX_RESULTS)
        skip = self.params.skip or 0

        query = model.Note.all_in_repo(self.repo)
        if self.params.min_entry_date:  # Scan forward.
            query = query.order('entry_date')
            query = query.filter('entry_date >=', self.params.min_entry_date)
        else:  # Show recent entries, scanning backward.
            query = query.order('-entry_date')

        if self.params.person_record_id:  # Show notes for a specific person.
            query = query.filter('person_record_id =',
                                 self.params.person_record_id)

        notes = query.fetch(max_results, offset=skip)
        updated = get_latest_entry_date(notes)

        # Show hidden notes as blank in the Note feed (melwitt)
        # http://code.google.com/p/googlepersonfinder/issues/detail?id=58
        make_hidden_notes_blank(notes)

        self.response.headers['Content-Type'] = 'application/xml'
        records = map(pfif_version.note_to_dict, notes)
        utils.optionally_filter_sensitive_fields(records, self.auth)
        atom_version.write_note_feed(
            self.response.out, records, self.request.url,
            self.env.netloc, NOTE_SUBTITLE_BASE + self.env.netloc, updated)
        utils.log_api_action(self, model.ApiActionLog.READ, 0, len(records))
Exemple #16
0
    def post(self):
        if not (self.auth and self.auth.subscribe_permission):
            return self.error(403, 'Missing or invalid authorization key')

        if not subscribe.is_email_valid(self.params.subscribe_email):
            return self.error(400, 'Invalid email address')

        person = model.Person.get(self.repo, self.params.id)
        if not person:
            return self.error(400, 'Invalid person_record_id')

        subscription = subscribe.subscribe_to(self, self.repo, person,
                                              self.params.subscribe_email,
                                              self.env.lang)
        utils.log_api_action(self, ApiActionLog.SUBSCRIBE)
        if not subscription:
            return self.info(200, 'Already subscribed')
        return self.info(200, 'Successfully subscribed')
Exemple #17
0
class Write(utils.BaseHandler):
    https_required = True

    def post(self):
        if not (self.auth and self.auth.domain_write_permission):
            self.info(
                403,
                message='Missing or invalid authorization key',
                style='plain')
            return

        source_domain = self.auth.domain_write_permission
        try:
            person_records, note_records = \
                pfif.parse_file(self.request.body_file)
        except Exception, e:
            self.info(400, message='Invalid XML: %s' % e, style='plain')
            return

        mark_notes_reviewed = bool(self.auth.mark_notes_reviewed)
        believed_dead_permission = bool(
            self.auth.believed_dead_permission)

        self.response.headers['Content-Type'] = 'application/xml; charset=utf-8'
        self.write('<?xml version="1.0"?>\n')
        self.write('<status:status ' +
                'xmlns:status="http://zesty.ca/pfif/1.4/status" ' +
                'xmlns:pfif="http://zesty.ca/pfif/1.4">\n')

        create_person = importer.create_person
        num_people_written, people_skipped, total = importer.import_records(
            self.repo, source_domain, create_person, person_records)
        self.write_status(
            'person', num_people_written, people_skipped, total,
            'person_record_id')

        create_note = importer.create_note
        num_notes_written, notes_skipped, total = importer.import_records(
            self.repo, source_domain, create_note, note_records,
            mark_notes_reviewed, believed_dead_permission, self)

        self.write_status(
            'note', num_notes_written, notes_skipped, total, 'note_record_id')

        self.write('</status:status>\n')
        utils.log_api_action(self, ApiActionLog.WRITE,
                             num_people_written, num_notes_written,
                             len(people_skipped), len(notes_skipped))
Exemple #18
0
        source_domain = self.auth.domain_write_permission
        records = importer.utf8_decoder(generate_note_record_ids(
            convert_time_fields(csv.reader(lines))))
        try:
            records = [complete_record_ids(r, source_domain) for r in records]
        except csv.Error, e:
            self.error(400, message=
                'The CSV file is formatted incorrectly. (%s)' % e)
            return

        notes_written, notes_skipped, notes_total = importer.import_records(
            self.repo, source_domain, importer.create_note, records,
            believed_dead_permission=self.auth.believed_dead_permission,
            omit_duplicate_notes=True)

        utils.log_api_action(self, ApiActionLog.WRITE,
                             0, notes_written, 0, len(notes_skipped))

        self.render('import.html',
                    formats=get_requested_formats(self.env.path),
                    stats=[
                        Struct(type='Note',
                               written=notes_written,
                               skipped=notes_skipped,
                               total=notes_total)],
                    **get_tag_params(self))

    def import_persons(self, lines):
        # TODO(ryok): support non-UTF8 encodings.

        source_domain = self.auth.domain_write_permission
        records = importer.utf8_decoder(convert_time_fields(csv.reader(lines)))