def edit(request, page_name): params = request.POST view_url = reverse('notepad:view', args=(page_name, )) redirect_url = view_url + '#bottom' if 'content' not in params: return HttpResponseRedirect(redirect_url) if is_bot_request(request): notes = [params.get('note')] return activity_notify(request, page_name, 'editing note', notes, redirect_url) # Get the Note requested. try: note_id = int(params.get('note')) except (TypeError, ValueError): log.warning('Invalid note id for editing: {!r}'.format( params.get('note'))) return HttpResponseRedirect(redirect_url) try: note = Note.objects.get(pk=note_id, page__name=page_name) except Note.DoesNotExist: log.warning( f'Visitor "{request.visit.visitor}" tried to submit an edit to non-existent note {note_id} ' f'or gave the wrong page {page_name!r}.') return HttpResponseRedirect(redirect_url) redirect_url = f'{view_url}#note_{note_id}' # Verify that everything looks valid. if note.protected and not is_admin_and_secure(request): # Prevent editing protected notes. log.warning( f'User tried to edit protected note {note.id} from page {note.page.name!r}.' ) return HttpResponseRedirect(redirect_url) if note.deleted: log.warning( f'User tried to edit deleted note {note.id} from page {note.page.name!r}.' ) return HttpResponseRedirect(redirect_url) if note.archived: log.warning( f'User tried to edit archived note {note.id} from page {note.page.name!r}.' ) return HttpResponseRedirect(redirect_url) # Double-check this note hasn't already been edited. if note.edited: log.warning( f'User tried to edit already-edited note {note.id} from page {note.page.name!r}.' ) return HttpResponseRedirect(redirect_url) if 'content' not in params: log.warning('No "content" key in query parameters.') return HttpResponseRedirect(redirect_url) new_note = lib.edit_note(note, params['content'], request.visit) if page_name in PROTECTED_PAGES: # Notify that someone edited a note from a protected page. activity_notify(request, page_name, f'editing a note', [note], blocked=False) redirect_url = f'{view_url}#note_{new_note.id}' return HttpResponseRedirect(redirect_url)
def moveform(request, page_name): view_url = reverse('notepad:view', args=(page_name, )) params = request.POST notes = get_notes_from_params(params, archived=False, deleted=False) if not is_admin_and_secure(request): # Prevent appearance of being able to move protected notes. notes = [note for note in notes if not note.protected] context = {'page': page_name, 'notes': notes} return render(request, 'notepad/moveform.tmpl', context)
def editform(request, page_name): view_url = reverse('notepad:view', args=(page_name, )) params = request.POST notes = get_notes_from_params(params) error = None warning = None if len(notes) == 0: note_ids = get_note_ids_from_params(params) notes_str = ', '.join([str(note_id) for note_id in note_ids]) log.warning( f'No valid note selected for editing. User selected {notes_str}') error = 'No valid note selected.' note = None elif len(notes) > 1: log.info('Multiple notes selected.') warning = 'Multiple notes selected. Editing only the first one.' note = notes[0] else: note = notes[0] # Prevent appearance of being able to edit protected, archived, or deleted notes. if not note: pass elif note.protected and not is_admin_and_secure(request): log.warning('Non-admin attempted to edit protected note {}.'.format( note.id)) error = 'This note is protected.' elif note.edited: log.warning(f'User attempted to edit already edited note {note.id}.') error = 'This note has already been edited.' elif note.archived: log.warning(f'User attempted to edit archived note {note.id}.') error = 'This note has been archived.' elif note.deleted: log.warning(f'User attempted to edit deleted note {note.id}.') error = 'This note has been deleted.' if error: context = {'page': page_name, 'error': error} #TODO: Return a non-200 HTTP status. return render(request, 'notepad/error.tmpl', context) elif note: notes = lib.get_notes(page_name, archived=False, deleted=False) lines = len(note.content.splitlines()) context = { 'page': page_name, 'note': note, 'notes': notes, 'rows': round(lines * 1.1) + 2, 'warning': warning, } return render(request, 'notepad/editform.tmpl', context) else: log.error( f'Ended up with neither a note ({note!r}) nor an error ({error!r}).' ) return HttpResponseRedirect(view_url)
def view_ip(request, ip=None): if ip is None: ip = request.visit.visitor.ip # Only allow regular users to view their own IP address. if is_admin_and_secure(request): admin = True else: admin = False ip = request.visit.visitor.ip # Get query parameters. params = request.GET format = params.get('format', 'html') # Get the IpInfos for the IP address. query = IpInfo.objects.filter(ip=ip).order_by('-timestamp') fields = ('timestamp', 'version', 'asn', 'isp', 'hostname', 'timezone', 'latitude', 'longitude', 'country', 'region', 'town', 'zip', 'label') if format == 'plain': if not query: return HttpResponse('No data on ip '+ip, content_type=settings.PLAINTEXT) response = HttpResponse(content_type=settings.PLAINTEXT) response.write(ip+':\n') response.write('\t'.join(fields)+'\n') for ipinfo in query: values = [str(getattr(ipinfo, field, None)) for field in fields] response.write('\t'.join(values)+'\n') return response else: ipinfos = [] try: last_ipinfo = None for ipinfo in query: if last_ipinfo is None: ipinfos.append(ipinfo) else: changed = False for field in fields: new_value = getattr(ipinfo, field, None) old_value = getattr(last_ipinfo, field, None) if field != 'timestamp' and new_value != old_value: changed = True if changed: ipinfos.append(ipinfo) last_ipinfo = ipinfo except DataError: # DataError is thrown on executing the query on an invalid ip address. log.warning('DataError on IP address'+ip) ipinfos = [] context = { 'ip':ip, 'admin':admin, 'timezone': set_timezone(request), # Set and retrieve timezone. 'ipinfos': ipinfos } return render(request, 'traffic/ips.tmpl', context)
def view(request, page): params = request.GET is_admin = is_admin_and_secure(request) editing = False if params.get('editing'): if is_admin: editing = True else: return HttpResponseRedirect(get_view_url(page)) context = {'editing': editing, 'editing_text': False, 'admin': is_admin} return show_page(request, page, context)
def _move_page(request, old_page_name, notes): view_url = reverse('notepad:view', args=(old_page_name, )) params = request.POST new_page_name = params.get('new_page') if new_page_name: view_url = reverse('notepad:view', args=(new_page_name, )) else: log.warning('No new_page received for move.') return HttpResponseRedirect(view_url) try: old_page = Page.objects.get(name=old_page_name) except Page.DoesNotExist: log.warning('Page {!r} does not exist.'.format(old_page_name)) return HttpResponseRedirect(view_url) try: new_page = Page.objects.get(name=new_page_name) except Page.DoesNotExist: new_page = Page(name=new_page_name) new_page.save() for note in notes: if note.page.name != old_page_name: log.warning( 'User tried to move note {!r} from page {!r}, but gave page name {!r}.' .format(note.id, note.page.name, old_page_name)) continue if note.protected and not is_admin_and_secure(request): # Prevent moving protected notes. continue move = Move( type='page', note=note, old_page=old_page, new_page=new_page, visit=request.visit, ) note.page = new_page # Save both or, if something goes wrong, neither. try: with transaction.atomic(): move.save() note.save() except django.db.Error as dbe: log.error('Error on saving Move or Note: {}'.format(dbe)) return HttpResponseRedirect(view_url)
def hide(request, page_name): params = request.POST action = params.get('action') notes = get_notes_from_params(params) admin = None if is_bot_request(request): activity_notify(request, page_name, f'{action}-ing', notes) elif action in ('archive', 'delete'): for note in notes: if note.page.name != page_name: log.warning( 'User attempted to {} note {} from page {!r}, but gave page name {!r}' .format(action, note.id, note.page.name, page_name)) continue if note.protected: if admin is None: admin = is_admin_and_secure(request) if not admin: log.warning( 'Non-admin attempted to delete protected note {!r}.'. format(note.id)) continue if action == 'archive': note.archived = True note.archiving_visit = request.visit elif action == 'delete': note.deleted = True note.deleting_visit = request.visit note.save() if page_name in PROTECTED_PAGES: # Notify that someone archived/deleted notes from a protected page. activity_notify(request, page_name, f'{action}-ing note(s)', notes, blocked=False) view_url = reverse('notepad:view', args=(page_name, )) return HttpResponseRedirect(view_url + '#bottom')
def monitor(request): # The query string parameters and their defaults. params = QueryParams() params.add('p', type=int, default=1, min=1) params.add('perpage', default=PER_PAGE_DEFAULT, type=int, min=1) params.add('include_me', type=boolish, default=False, choices=(True, False)) params.add('bot_thres', default=None, type=int) params.add('user', default=None, type=int) params.parse(request.GET) # If one of the parameters was invalid, redirect to a fixed url. # - QueryParams object will automatically set the parameter to a valid value. if params.invalid_value: return HttpResponseRedirect(reverse('traffic_monitor')+str(params)) admin = is_admin_and_secure(request) # Non-admins can only view their own traffic. if not admin: this_user = request.visit.visitor.user.id if params['user']: # If they gave a user, but it's not themselves, redirect back to themself. if params['user'] != this_user: return HttpResponseRedirect(reverse('traffic_monitor')+str(params.but_with(user=None))) else: # If they gave no user, that's fine. Silently show only themself. params['user'] = this_user params.params['user'].default = this_user # Obtain visits list from database. if params['user'] is not None: visits = Visit.objects.filter(visitor__user__id=params['user']) elif params['include_me']: visits = Visit.objects.all() else: visits = Visit.objects.exclude(visitor__user__id=1) # Exclude robots, if requested. if params['bot_thres'] is not None and params['user'] is None: visits = visits.filter(visitor__bot_score__lt=params['bot_thres']) # Order by id. visits = visits.order_by('-id') # Create a Paginator. pages = django.core.paginator.Paginator(visits, params['perpage']) try: page = pages.page(params['p']) except django.core.paginator.EmptyPage: return HttpResponseRedirect(reverse('traffic_monitor')+str(params.but_with(p=pages.num_pages))) # Construct the navigation links. links = collections.OrderedDict() if page.has_previous(): links['Later'] = str(params.but_with(p=page.previous_page_number())) if admin: if params['include_me']: links['Hide me'] = str(params.but_with(include_me=None)) else: links['Include me'] = str(params.but_with(include_me=True)) if params['bot_thres'] is not None and params['bot_thres'] != 0: links['Show all'] = str(params.but_with(bot_thres=None)) if params['bot_thres'] is None or params['bot_thres'] > categorize.SCORES['bot_in_ua']: links['Hide robots'] = str(params.but_with(bot_thres=categorize.SCORES['bot_in_ua'])) if params['bot_thres'] is None or params['bot_thres'] > categorize.SCORES['sent_cookies']+1: links['Show humans'] = str(params.but_with(bot_thres=categorize.SCORES['sent_cookies']+1)) if page.has_next(): links['Earlier'] = str(params.but_with(p=page.next_page_number())) context = { 'visits': page, 'admin':admin, 'links': links, 'this_visit': request.visit, 'timezone': set_timezone(request), # Set and retrieve timezone. 'ua_exact_thres': categorize.SCORES['ua_exact'], 'referrer_exact_thres': categorize.SCORES['referrer_exact'], } return render(request, 'traffic/monitor.tmpl', context)
def view(request, page_name): params = QueryParams() params.add('note', type=int, default=None) params.add('version', type=int, default=None) params.add('format', default='html', choices=('html', 'plain')) params.add('admin', type=boolish, default=False, choices=(True, False)) params.add('archived', type=boolish, default=False, choices=(True, False)) params.add('deleted', type=boolish, default=False, choices=(True, False)) params.add('select', default='none', choices=('all', 'none')) params.parse(request.GET) # If one of the parameters was invalid, redirect to a fixed url. # - QueryParams object will automatically set the parameter to a valid value. if params.invalid_value: return HttpResponseRedirect( reverse('notepad:view', args=(page_name, )) + str(params)) # Only allow showing deleted notes to the admin over HTTPS. is_admin = is_admin_and_secure(request) if not is_admin and (params['admin'] or params['deleted']): query_str = str(params.but_with(admin=False, deleted=False)) return HttpResponseRedirect( reverse('notepad:view', args=(page_name, )) + query_str) # Fetch the note(s). if params['version']: notes = lib.get_notes_from_ids([params['version']], page_name, params['archived'], params['deleted']) elif params['note']: note = lib.get_latest_version(params['note'], page_name, params['archived'], params['deleted']) notes = [note] else: notes = lib.get_notes(page_name, params['archived'], params['deleted']) # Bundle up the data and display the notes. if params['format'] == 'plain': contents = [note.content for note in notes] return HttpResponse('\n\n'.join(contents), content_type=settings.PLAINTEXT) else: links = collections.OrderedDict() randomness = rand.randint(1, 1000000) links['☑ Select all'] = str( params.but_with(select='all', reload=randomness)) links['□ Select none'] = str( params.but_with(select='none', reload=randomness)) if is_admin: if params['admin']: links['Admin off'] = str(params.but_with(admin=False)) else: links['Admin on'] = str(params.but_with(admin=True)) if params['archived']: links['Hide archived'] = str(params.but_with(archived=False)) else: links['Show archived'] = str(params.but_with(archived=True)) if is_admin: if params['deleted']: links['Hide deleted'] = str(params.but_with(deleted=False)) else: links['Show deleted'] = str(params.but_with(deleted=True)) context = { 'page': page_name, 'notes': notes, 'links': links, 'admin_view': params['admin'], 'select': params['select'], } return render(request, 'notepad/view.tmpl', context)
def _move_order(request, page_name, notes, direction): page_notes = lib.get_notes(page_name, archived=False, deleted=False) # Save old display_orders. old_orders = {} for note in page_notes: old_orders[note.id] = note.display_order # Go through the notes in the opposite direction they're moving in. # E.g. if we're moving notes down, then start from the last note and move up. reverse_order = direction == 'down' sorted_notes = sorted(page_notes, reverse=reverse_order, key=lambda note: (note.display_order, note.id)) # Set new display_orders. for i in range(len(sorted_notes)): note = sorted_notes[i] if i == 0: # We can't move this note earlier, since it's already at the end. continue last_note = sorted_notes[i - 1] if note not in notes: # This isn't one of the notes being moved. continue if last_note in notes: # Don't swap a note with another note being moved. They should all move in a block. # If a bunch of notes in a block are already at the end, the first rule (i == 0) prevents the # note at the end from moving, and this prevents the others in the block from moving past it. continue if note.protected and not is_admin_and_secure(request): # Prevent moving protected notes. continue # Swap display orders. note.display_order, last_note.display_order = last_note.display_order, note.display_order # Swap positions in the list. sorted_notes[i] = last_note sorted_notes[i - 1] = note # Create Moves and save changes to database. for note in page_notes: if note.page.name != page_name: log.warning( 'User tried to move note {!r} order on page {!r}, but gave page name {!r}.' .format(note.id, note.page.name, page_name)) continue if note.display_order == old_orders[note.id]: continue move = Move( type='order', note=note, old_display_order=old_orders[note.id], new_display_order=note.display_order, visit=request.visit, ) log.info('Moving note {} display_order from {} to {}.'.format( note.id, move.old_display_order, move.new_display_order)) # Save both or, if something goes wrong, neither. try: with transaction.atomic(): move.save() note.save() except django.db.Error as dbe: log.error('Error on saving Move or Note: {}'.format(dbe)) view_url = reverse('notepad:view', args=(page_name, )) return HttpResponseRedirect(view_url + '#bottom')