First attempt at online permissions.

master
voussoir 2023-02-04 13:49:42 -08:00
parent e78a667ee3
commit ce30077013
10 changed files with 181 additions and 13 deletions

View File

@ -4,29 +4,46 @@ Use etiquette_flask_dev.py or etiquette_flask_prod.py.
'''
import flask; from flask import request
import functools
import json
import mimetypes
import traceback
from voussoirkit import bytestring
from voussoirkit import configlayers
from voussoirkit import flasktools
from voussoirkit import pathclass
from voussoirkit import vlogging
import etiquette
from . import client_caching
from . import jinja_filters
from . import permissions
from . import sessions
log = vlogging.getLogger(__name__)
# Constants ########################################################################################
DEFAULT_SERVER_CONFIG = {
'anonymous_read': True,
'anonymous_write': True,
}
BROWSER_CACHE_DURATION = 180
# Flask init #######################################################################################
# __file__ = .../etiquette_flask/backend/common.py
# root_dir = .../etiquette_flask
root_dir = pathclass.Path(__file__).parent.parent
P = None
TEMPLATE_DIR = root_dir.with_child('templates')
STATIC_DIR = root_dir.with_child('static')
FAVICON_PATH = STATIC_DIR.with_child('favicon.png')
BROWSER_CACHE_DURATION = 180
SERVER_CONFIG_FILENAME = 'etiquette_flask_config.json'
site = flask.Flask(
__name__,
@ -37,6 +54,7 @@ site.config.update(
SEND_FILE_MAX_AGE_DEFAULT=BROWSER_CACHE_DURATION,
TEMPLATES_AUTO_RELOAD=True,
)
site.server_config = None
site.jinja_env.add_extension('jinja2.ext.do')
site.jinja_env.trim_blocks = True
site.jinja_env.lstrip_blocks = True
@ -49,6 +67,7 @@ file_etag_manager = client_caching.FileEtagManager(
max_filesize=5 * bytestring.MEBIBYTE,
max_age=BROWSER_CACHE_DURATION,
)
permission_manager = permissions.PermissionManager(site)
# Response wrappers ################################################################################
@ -80,10 +99,18 @@ def before_request():
if site.localhost_only and not request.is_localhost:
return flask.abort(403)
# Since we don't define this route, I can't just add this where it belongs.
# Sorry.
if request.url_rule.rule == '/static/<path:filename>':
permission_manager.global_public()
session_manager._before_request(request)
@site.after_request
def after_request(response):
if response.status_code < 400 and not hasattr(request, 'checked_permissions'):
log.error('You forgot to set checked_permissions for ' + request.path)
return flask.abort(500)
response = flasktools.gzip_response(request, response)
response = session_manager._after_request(response)
return response
@ -277,3 +304,22 @@ def send_file(filepath, override_mimetype=None):
def init_photodb(*args, **kwargs):
global P
P = etiquette.photodb.PhotoDB.closest_photodb(*args, **kwargs)
load_config()
def load_config() -> None:
log.debug('Loading server config file.')
config_file = P.data_directory.with_child(SERVER_CONFIG_FILENAME)
(config, needs_rewrite) = configlayers.load_file(
filepath=config_file,
default_config=DEFAULT_SERVER_CONFIG,
)
site.server_config = config
if needs_rewrite:
save_config()
def save_config() -> None:
log.debug('Saving server config file.')
config_file = P.data_directory.with_child(SERVER_CONFIG_FILENAME)
with config_file.open('w', encoding='utf-8') as handle:
handle.write(json.dumps(site.server_config, indent=4, sort_keys=True))

View File

@ -15,8 +15,7 @@ session_manager = common.session_manager
@site.route('/admin')
def get_admin():
if not request.is_localhost:
flask.abort(403)
common.permission_manager.admin()
counts = dotdict.DotDict({
'albums': common.P.get_album_count(),
@ -36,8 +35,7 @@ def get_admin():
@site.route('/admin/dbdownload')
def get_dbdump():
if not request.is_localhost:
flask.abort(403)
common.permission_manager.admin()
with common.P.transaction:
binary = common.P.database_filepath.read('rb')
@ -52,24 +50,23 @@ def get_dbdump():
@site.route('/admin/clear_sessions', methods=['POST'])
def post_clear_sessions():
if not request.is_localhost:
return flasktools.json_response({}, status=403)
common.permission_manager.admin()
session_manager.clear()
return flasktools.json_response({})
@site.route('/admin/reload_config', methods=['POST'])
def post_reload_config():
if not request.is_localhost:
return flasktools.json_response({}, status=403)
common.permission_manager.admin()
common.P.load_config()
common.load_config()
return flasktools.json_response({})
@site.route('/admin/uncache', methods=['POST'])
def post_uncache():
if not request.is_localhost:
return flasktools.json_response({}, status=403)
common.permission_manager.admin()
with common.P.transaction:
for cache in common.P.caches.values():

View File

@ -17,6 +17,7 @@ session_manager = common.session_manager
@site.route('/album/<album_id>')
def get_album_html(album_id):
common.permission_manager.basic()
album = common.P_album(album_id, response_type='html')
response = common.render_template(
request,
@ -28,12 +29,14 @@ def get_album_html(album_id):
@site.route('/album/<album_id>.json')
def get_album_json(album_id):
common.permission_manager.basic()
album = common.P_album(album_id, response_type='json')
album = album.jsonify()
return flasktools.json_response(album)
@site.route('/album/<album_id>.zip')
def get_album_zip(album_id):
common.permission_manager.basic()
album = common.P_album(album_id, response_type='html')
recursive = request.args.get('recursive', True)
@ -58,6 +61,7 @@ def get_album_zip(album_id):
@site.route('/album/<album_id>/add_child', methods=['POST'])
@flasktools.required_fields(['child_id'], forbid_whitespace=True)
def post_album_add_child(album_id):
common.permission_manager.basic()
child_ids = stringtools.comma_space_split(request.form['child_id'])
with common.P.transaction:
album = common.P_album(album_id, response_type='json')
@ -71,6 +75,7 @@ def post_album_add_child(album_id):
@site.route('/album/<album_id>/remove_child', methods=['POST'])
@flasktools.required_fields(['child_id'], forbid_whitespace=True)
def post_album_remove_child(album_id):
common.permission_manager.basic()
child_ids = stringtools.comma_space_split(request.form['child_id'])
with common.P.transaction:
album = common.P_album(album_id, response_type='json')
@ -81,6 +86,7 @@ def post_album_remove_child(album_id):
@site.route('/album/<album_id>/remove_thumbnail_photo', methods=['POST'])
def post_album_remove_thumbnail_photo(album_id):
common.permission_manager.basic()
with common.P.transaction:
album = common.P_album(album_id, response_type='json')
album.set_thumbnail_photo(None)
@ -88,6 +94,7 @@ def post_album_remove_thumbnail_photo(album_id):
@site.route('/album/<album_id>/refresh_directories', methods=['POST'])
def post_album_refresh_directories(album_id):
common.permission_manager.basic()
with common.P.transaction:
album = common.P_album(album_id, response_type='json')
for directory in album.get_associated_directories():
@ -100,6 +107,7 @@ def post_album_refresh_directories(album_id):
@site.route('/album/<album_id>/set_thumbnail_photo', methods=['POST'])
@flasktools.required_fields(['photo_id'], forbid_whitespace=True)
def post_album_set_thumbnail_photo(album_id):
common.permission_manager.basic()
with common.P.transaction:
album = common.P_album(album_id, response_type='json')
photo = common.P_photo(request.form['photo_id'], response_type='json')
@ -114,7 +122,7 @@ def post_album_add_photo(album_id):
'''
Add a photo or photos to this album.
'''
common.permission_manager.basic()
photo_ids = stringtools.comma_space_split(request.form['photo_id'])
with common.P.transaction:
album = common.P_album(album_id, response_type='json')
@ -129,6 +137,7 @@ def post_album_remove_photo(album_id):
'''
Remove a photo or photos from this album.
'''
common.permission_manager.basic()
photo_ids = stringtools.comma_space_split(request.form['photo_id'])
with common.P.transaction:
album = common.P_album(album_id, response_type='json')
@ -144,6 +153,7 @@ def post_album_add_tag(album_id):
'''
Apply a tag to every photo in the album.
'''
common.permission_manager.basic()
response = {}
with common.P.transaction:
album = common.P_album(album_id, response_type='json')
@ -168,6 +178,7 @@ def post_album_edit(album_id):
'''
Edit the title / description.
'''
common.permission_manager.basic()
title = request.form.get('title', None)
description = request.form.get('description', None)
@ -180,6 +191,7 @@ def post_album_edit(album_id):
@site.route('/album/<album_id>/show_in_folder', methods=['POST'])
def post_album_show_in_folder(album_id):
common.permission_manager.basic()
if not request.is_localhost:
flask.abort(403)
@ -199,6 +211,7 @@ def post_album_show_in_folder(album_id):
# Album listings ###################################################################################
@site.route('/all_albums.json')
@common.permission_manager.basic_decorator
@flasktools.cached_endpoint(max_age=15)
def get_all_album_names():
all_albums = {album.id: album.display_name for album in common.P.get_albums()}
@ -207,6 +220,7 @@ def get_all_album_names():
@site.route('/albums')
def get_albums_html():
common.permission_manager.basic()
albums = list(common.P.get_root_albums())
albums.sort(key=lambda x: x.display_name.lower())
response = common.render_template(
@ -219,6 +233,7 @@ def get_albums_html():
@site.route('/albums.json')
def get_albums_json():
common.permission_manager.basic()
albums = list(common.P.get_albums())
albums.sort(key=lambda x: x.display_name.lower())
albums = [album.jsonify(include_photos=False) for album in albums]
@ -228,6 +243,7 @@ def get_albums_json():
@site.route('/albums/create_album', methods=['POST'])
def post_albums_create():
common.permission_manager.basic()
title = request.form.get('title', None)
description = request.form.get('description', None)
parent_id = request.form.get('parent_id', None)
@ -246,6 +262,7 @@ def post_albums_create():
@site.route('/album/<album_id>/delete', methods=['POST'])
def post_album_delete(album_id):
common.permission_manager.basic()
with common.P.transaction:
album = common.P_album(album_id, response_type='json')
album.delete()

View File

@ -10,10 +10,12 @@ session_manager = common.session_manager
@site.route('/')
def root():
common.permission_manager.global_public()
motd = random.choice(common.P.config['motd_strings'])
return common.render_template(request, 'root.html', motd=motd)
@site.route('/favicon.ico')
@site.route('/favicon.png')
def favicon():
common.permission_manager.global_public()
return flask.send_file(common.FAVICON_PATH.absolute_path)

View File

@ -13,12 +13,14 @@ session_manager = common.session_manager
@site.route('/bookmark/<bookmark_id>.json')
def get_bookmark_json(bookmark_id):
common.permission_manager.basic()
bookmark = common.P_bookmark(bookmark_id, response_type='json')
response = bookmark.jsonify()
return flasktools.json_response(response)
@site.route('/bookmark/<bookmark_id>/edit', methods=['POST'])
def post_bookmark_edit(bookmark_id):
common.permission_manager.basic()
with common.P.transaction:
bookmark = common.P_bookmark(bookmark_id, response_type='json')
# Emptystring is okay for titles, but not for URL.
@ -34,6 +36,7 @@ def post_bookmark_edit(bookmark_id):
@site.route('/bookmarks.atom')
def get_bookmarks_atom():
common.permission_manager.basic()
bookmarks = common.P.get_bookmarks()
response = etiquette.helpers.make_atom_feed(
bookmarks,
@ -45,11 +48,13 @@ def get_bookmarks_atom():
@site.route('/bookmarks')
def get_bookmarks_html():
common.permission_manager.basic()
bookmarks = list(common.P.get_bookmarks())
return common.render_template(request, 'bookmarks.html', bookmarks=bookmarks)
@site.route('/bookmarks.json')
def get_bookmarks_json():
common.permission_manager.basic()
bookmarks = [b.jsonify() for b in common.P.get_bookmarks()]
return flasktools.json_response(bookmarks)
@ -58,6 +63,7 @@ def get_bookmarks_json():
@site.route('/bookmarks/create_bookmark', methods=['POST'])
@flasktools.required_fields(['url'], forbid_whitespace=True)
def post_bookmark_create():
common.permission_manager.basic()
url = request.form['url']
title = request.form.get('title', None)
user = session_manager.get(request).user
@ -69,6 +75,7 @@ def post_bookmark_create():
@site.route('/bookmark/<bookmark_id>/delete', methods=['POST'])
def post_bookmark_delete(bookmark_id):
common.permission_manager.basic()
with common.P.transaction:
bookmark = common.P_bookmark(bookmark_id, response_type='json')
bookmark.delete()

View File

@ -25,11 +25,13 @@ photo_download_zip_tokens = cacheclass.Cache(maxlen=100)
@site.route('/photo/<photo_id>')
def get_photo_html(photo_id):
common.permission_manager.basic()
photo = common.P_photo(photo_id, response_type='html')
return common.render_template(request, 'photo.html', photo=photo)
@site.route('/photo/<photo_id>.json')
def get_photo_json(photo_id):
common.permission_manager.basic()
photo = common.P_photo(photo_id, response_type='json')
photo = photo.jsonify()
photo = flasktools.json_response(photo)
@ -38,6 +40,7 @@ def get_photo_json(photo_id):
@site.route('/photo/<photo_id>/download')
@site.route('/photo/<photo_id>/download/<basename>')
def get_file(photo_id, basename=None):
common.permission_manager.basic()
photo_id = photo_id.split('.')[0]
photo = common.P.get_photo(photo_id)
@ -63,6 +66,7 @@ def get_file(photo_id, basename=None):
@site.route('/photo/<photo_id>/thumbnail')
@site.route('/photo/<photo_id>/thumbnail/<basename>')
@common.permission_manager.basic_decorator
@flasktools.cached_endpoint(max_age=common.BROWSER_CACHE_DURATION)
def get_thumbnail(photo_id, basename=None):
photo_id = photo_id.split('.')[0]
@ -90,6 +94,7 @@ def get_thumbnail(photo_id, basename=None):
@site.route('/photo/<photo_id>/delete', methods=['POST'])
def post_photo_delete(photo_id):
common.permission_manager.basic()
delete_file = request.form.get('delete_file', False)
delete_file = stringtools.truthystring(delete_file)
with common.P.transaction:
@ -122,6 +127,7 @@ def post_photo_add_tag(photo_id):
'''
Add a tag to this photo.
'''
common.permission_manager.basic()
response = post_photo_add_remove_tag_core(
photo_ids=photo_id,
tagname=request.form['tagname'],
@ -135,6 +141,7 @@ def post_photo_copy_tags(photo_id):
'''
Copy the tags from another photo.
'''
common.permission_manager.basic()
with common.P.transaction:
photo = common.P_photo(photo_id, response_type='json')
other = common.P_photo(request.form['other_photo'], response_type='json')
@ -147,6 +154,7 @@ def post_photo_remove_tag(photo_id):
'''
Remove a tag from this photo.
'''
common.permission_manager.basic()
response = post_photo_add_remove_tag_core(
photo_ids=photo_id,
tagname=request.form['tagname'],
@ -157,6 +165,7 @@ def post_photo_remove_tag(photo_id):
@site.route('/batch/photos/add_tag', methods=['POST'])
@flasktools.required_fields(['photo_ids', 'tagname'], forbid_whitespace=True)
def post_batch_photos_add_tag():
common.permission_manager.basic()
response = post_photo_add_remove_tag_core(
photo_ids=request.form['photo_ids'],
tagname=request.form['tagname'],
@ -167,6 +176,7 @@ def post_batch_photos_add_tag():
@site.route('/batch/photos/remove_tag', methods=['POST'])
@flasktools.required_fields(['photo_ids', 'tagname'], forbid_whitespace=True)
def post_batch_photos_remove_tag():
common.permission_manager.basic()
response = post_photo_add_remove_tag_core(
photo_ids=request.form['photo_ids'],
tagname=request.form['tagname'],
@ -178,6 +188,7 @@ def post_batch_photos_remove_tag():
@site.route('/photo/<photo_id>/generate_thumbnail', methods=['POST'])
def post_photo_generate_thumbnail(photo_id):
common.permission_manager.basic()
special = request.form.to_dict()
with common.P.transaction:
@ -212,17 +223,20 @@ def post_photo_refresh_metadata_core(photo_ids):
@site.route('/photo/<photo_id>/refresh_metadata', methods=['POST'])
def post_photo_refresh_metadata(photo_id):
common.permission_manager.basic()
response = post_photo_refresh_metadata_core(photo_ids=photo_id)
return response
@site.route('/batch/photos/refresh_metadata', methods=['POST'])
@flasktools.required_fields(['photo_ids'], forbid_whitespace=True)
def post_batch_photos_refresh_metadata():
common.permission_manager.basic()
response = post_photo_refresh_metadata_core(photo_ids=request.form['photo_ids'])
return response
@site.route('/photo/<photo_id>/set_searchhidden', methods=['POST'])
def post_photo_set_searchhidden(photo_id):
common.permission_manager.basic()
with common.P.transaction:
photo = common.P_photo(photo_id, response_type='json')
photo.set_searchhidden(True)
@ -230,6 +244,7 @@ def post_photo_set_searchhidden(photo_id):
@site.route('/photo/<photo_id>/unset_searchhidden', methods=['POST'])
def post_photo_unset_searchhidden(photo_id):
common.permission_manager.basic()
with common.P.transaction:
photo = common.P_photo(photo_id, response_type='json')
photo.set_searchhidden(False)
@ -249,6 +264,7 @@ def post_batch_photos_searchhidden_core(photo_ids, searchhidden):
@site.route('/photo/<photo_id>/show_in_folder', methods=['POST'])
def post_photo_show_in_folder(photo_id):
common.permission_manager.basic()
if not request.is_localhost:
flask.abort(403)
@ -267,6 +283,7 @@ def post_photo_show_in_folder(photo_id):
@site.route('/batch/photos/set_searchhidden', methods=['POST'])
@flasktools.required_fields(['photo_ids'], forbid_whitespace=True)
def post_batch_photos_set_searchhidden():
common.permission_manager.basic()
photo_ids = request.form['photo_ids']
response = post_batch_photos_searchhidden_core(photo_ids=photo_ids, searchhidden=True)
return response
@ -274,6 +291,7 @@ def post_batch_photos_set_searchhidden():
@site.route('/batch/photos/unset_searchhidden', methods=['POST'])
@flasktools.required_fields(['photo_ids'], forbid_whitespace=True)
def post_batch_photos_unset_searchhidden():
common.permission_manager.basic()
photo_ids = request.form['photo_ids']
response = post_batch_photos_searchhidden_core(photo_ids=photo_ids, searchhidden=False)
return response
@ -282,6 +300,7 @@ def post_batch_photos_unset_searchhidden():
@site.route('/clipboard')
def get_clipboard_page():
common.permission_manager.basic()
return common.render_template(request, 'clipboard.html')
@site.route('/batch/photos', methods=['POST'])
@ -290,6 +309,7 @@ def post_batch_photos():
'''
Return a list of photo.jsonify() for each requested photo id.
'''
common.permission_manager.basic()
photo_ids = request.form['photo_ids']
photo_ids = stringtools.comma_space_split(photo_ids)
@ -302,6 +322,7 @@ def post_batch_photos():
@site.route('/batch/photos/photo_card', methods=['POST'])
@flasktools.required_fields(['photo_ids'], forbid_whitespace=True)
def post_batch_photos_photo_cards():
common.permission_manager.basic()
photo_ids = request.form['photo_ids']
photo_ids = stringtools.comma_space_split(photo_ids)
@ -333,6 +354,7 @@ def get_batch_photos_download_zip(zip_token):
After the user has generated their zip token, they can retrieve
that zip file.
'''
common.permission_manager.basic()
zip_token = zip_token.split('.')[0]
try:
photo_ids = photo_download_zip_tokens[zip_token]
@ -362,6 +384,7 @@ def post_batch_photos_download_zip():
so the way this works is we generate a token representing the photoset
that they want, and then they can retrieve the zip itself via GET.
'''
common.permission_manager.basic()
photo_ids = request.form['photo_ids']
photo_ids = stringtools.comma_space_split(photo_ids)
@ -436,6 +459,7 @@ def get_search_core():
@site.route('/search_embed')
def get_search_embed():
common.permission_manager.basic()
search = get_search_core()
response = common.render_template(
request,
@ -447,6 +471,8 @@ def get_search_embed():
@site.route('/search')
def get_search_html():
common.permission_manager.basic()
search = get_search_core()
search.kwargs.view = request.args.get('view', 'grid')
@ -496,6 +522,7 @@ def get_search_html():
@site.route('/search.atom')
def get_search_atom():
common.permission_manager.basic()
search = get_search_core()
soup = etiquette.helpers.make_atom_feed(
search.results,
@ -508,6 +535,7 @@ def get_search_atom():
@site.route('/search.json')
def get_search_json():
common.permission_manager.basic()
search = get_search_core()
response = search.jsonify()
return flasktools.json_response(response)
@ -516,5 +544,6 @@ def get_search_json():
@site.route('/swipe')
def get_swipe():
common.permission_manager.basic()
response = common.render_template(request, 'swipe.html')
return response

View File

@ -15,11 +15,13 @@ session_manager = common.session_manager
@site.route('/tags/<specific_tag>')
@site.route('/tags/<specific_tag>.json')
def get_tags_specific_redirect(specific_tag):
common.permission_manager.basic()
return flask.redirect(request.url.replace('/tags/', '/tag/'))
@site.route('/tagid/<tag_id>')
@site.route('/tagid/<tag_id>.json')
def get_tag_id_redirect(tag_id):
common.permission_manager.basic()
if request.path.endswith('.json'):
tag = common.P_tag_id(tag_id, response_type='json')
else:
@ -31,6 +33,7 @@ def get_tag_id_redirect(tag_id):
@site.route('/tag/<specific_tag_name>.json')
def get_tag_json(specific_tag_name):
common.permission_manager.basic()
specific_tag = common.P_tag(specific_tag_name, response_type='json')
if specific_tag.name != specific_tag_name:
new_url = f'/tag/{specific_tag.name}.json' + request.query_string.decode('utf-8')
@ -44,6 +47,7 @@ def get_tag_json(specific_tag_name):
@site.route('/tag/<tagname>/edit', methods=['POST'])
def post_tag_edit(tagname):
common.permission_manager.basic()
with common.P.transaction:
tag = common.P_tag(tagname, response_type='json')
name = request.form.get('name', '').strip()
@ -59,6 +63,7 @@ def post_tag_edit(tagname):
@site.route('/tag/<tagname>/add_child', methods=['POST'])
@flasktools.required_fields(['child_name'], forbid_whitespace=True)
def post_tag_add_child(tagname):
common.permission_manager.basic()
with common.P.transaction:
parent = common.P_tag(tagname, response_type='json')
child = common.P_tag(request.form['child_name'], response_type='json')
@ -69,6 +74,7 @@ def post_tag_add_child(tagname):
@site.route('/tag/<tagname>/add_synonym', methods=['POST'])
@flasktools.required_fields(['syn_name'], forbid_whitespace=True)
def post_tag_add_synonym(tagname):
common.permission_manager.basic()
syn_name = request.form['syn_name']
with common.P.transaction:
@ -81,6 +87,7 @@ def post_tag_add_synonym(tagname):
@site.route('/tag/<tagname>/remove_child', methods=['POST'])
@flasktools.required_fields(['child_name'], forbid_whitespace=True)
def post_tag_remove_child(tagname):
common.permission_manager.basic()
with common.P.transaction:
parent = common.P_tag(tagname, response_type='json')
child = common.P_tag(request.form['child_name'], response_type='json')
@ -91,6 +98,7 @@ def post_tag_remove_child(tagname):
@site.route('/tag/<tagname>/remove_synonym', methods=['POST'])
@flasktools.required_fields(['syn_name'], forbid_whitespace=True)
def post_tag_remove_synonym(tagname):
common.permission_manager.basic()
syn_name = request.form['syn_name']
with common.P.transaction:
@ -103,6 +111,7 @@ def post_tag_remove_synonym(tagname):
# Tag listings #####################################################################################
@site.route('/all_tags.json')
@common.permission_manager.basic_decorator
@flasktools.cached_endpoint(max_age=15)
def get_all_tag_names():
all_tags = list(common.P.get_all_tag_names())
@ -113,6 +122,7 @@ def get_all_tag_names():
@site.route('/tag/<specific_tag_name>')
@site.route('/tags')
def get_tags_html(specific_tag_name=None):
common.permission_manager.basic()
if specific_tag_name is None:
specific_tag = None
else:
@ -151,6 +161,7 @@ def get_tags_html(specific_tag_name=None):
@site.route('/tags.json')
def get_tags_json():
common.permission_manager.basic()
include_synonyms = request.args.get('synonyms')
include_synonyms = include_synonyms is None or stringtools.truthystring(include_synonyms)
@ -164,6 +175,7 @@ def get_tags_json():
@site.route('/tags/create_tag', methods=['POST'])
@flasktools.required_fields(['name'], forbid_whitespace=True)
def post_tag_create():
common.permission_manager.basic()
name = request.form['name']
description = request.form.get('description', None)
@ -175,6 +187,7 @@ def post_tag_create():
@site.route('/tags/easybake', methods=['POST'])
@flasktools.required_fields(['easybake_string'], forbid_whitespace=True)
def post_tag_easybake():
common.permission_manager.basic()
easybake_string = request.form['easybake_string']
with common.P.transaction:
@ -184,6 +197,7 @@ def post_tag_easybake():
@site.route('/tag/<tagname>/delete', methods=['POST'])
def post_tag_delete(tagname):
common.permission_manager.basic()
with common.P.transaction:
tag = common.P_tag(tagname, response_type='json')
tag.delete()

View File

@ -14,11 +14,13 @@ session_manager = common.session_manager
@site.route('/user/<username>')
def get_user_html(username):
common.permission_manager.basic()
user = common.P_user(username, response_type='html')
return common.render_template(request, 'user.html', user=user)
@site.route('/user/<username>.json')
def get_user_json(username):
common.permission_manager.basic()
user = common.P_user(username, response_type='json')
user = user.jsonify()
return flasktools.json_response(user)
@ -26,6 +28,7 @@ def get_user_json(username):
@site.route('/userid/<user_id>')
@site.route('/userid/<user_id>.json')
def get_user_id_redirect(user_id):
common.permission_manager.basic()
if request.path.endswith('.json'):
user = common.P_user_id(user_id, response_type='json')
else:
@ -37,6 +40,7 @@ def get_user_id_redirect(user_id):
@site.route('/user/<username>/edit', methods=['POST'])
def post_user_edit(username):
common.permission_manager.basic()
if not request.session:
return flasktools.json_response(etiquette.exceptions.Unauthorized().jsonify(), status=403)
user = common.P_user(username, response_type='json')
@ -54,6 +58,7 @@ def post_user_edit(username):
@site.route('/login', methods=['GET'])
def get_login():
common.permission_manager.global_public()
response = common.render_template(
request,
'login.html',
@ -66,6 +71,7 @@ def get_login():
@site.route('/login', methods=['POST'])
@flasktools.required_fields(['username', 'password'])
def post_login():
common.permission_manager.global_public()
if request.session.user:
exc = etiquette.exceptions.AlreadySignedIn()
response = exc.jsonify()
@ -96,6 +102,7 @@ def post_login():
@site.route('/logout', methods=['POST'])
def post_logout():
common.permission_manager.basic()
session_manager.remove(request)
response = flasktools.json_response({})
return response
@ -104,11 +111,13 @@ def post_logout():
@site.route('/register', methods=['GET'])
def get_register():
common.permission_manager.global_public()
return flask.redirect('/login')
@site.route('/register', methods=['POST'])
@flasktools.required_fields(['username', 'password_1', 'password_2'])
def post_register():
common.permission_manager.global_public()
if request.session.user:
exc = etiquette.exceptions.AlreadySignedIn()
response = exc.jsonify()

View File

@ -0,0 +1,43 @@
import flask; from flask import request
import functools
from voussoirkit import vlogging
log = vlogging.getLogger(__name__)
class PermissionManager:
def __init__(self, site):
self.site = site
def admin(self):
if request.is_localhost:
request.checked_permissions = True
return True
else:
return flask.abort(403)
def basic(self):
if request.method not in {'GET', 'POST'}:
return flask.abort(405)
elif request.is_localhost:
request.checked_permissions = True
return True
elif request.method == 'GET' and self.site.server_config['anonymous_read'] or request.session.user:
request.checked_permissions = True
return True
elif request.method == 'POST' and self.site.server_config['anonymous_write'] or request.session.user:
request.checked_permissions = True
return True
else:
return flask.abort(403)
def basic_decorator(self, endpoint):
log.debug('Decorating %s with basic_decorator.', endpoint)
@functools.wraps(endpoint)
def wrapped(*args, **kwargs):
self.basic()
return endpoint(*args, **kwargs)
return wrapped
def global_public(self):
request.checked_permissions = True

View File

@ -1,6 +1,9 @@
'''
This file is the gevent launcher for local / development use.
'''
from voussoirkit import vlogging
vlogging.earlybird_config()
import gevent.monkey; gevent.monkey.patch_all()
import werkzeug.middleware.proxy_fix
@ -11,6 +14,7 @@ import sys
from voussoirkit import betterhelp
from voussoirkit import pathclass
from voussoirkit import operatornotify
from voussoirkit import vlogging
log = vlogging.getLogger(__name__, 'etiquette_flask_dev')
@ -79,7 +83,7 @@ def etiquette_flask_launch_argparse(args):
use_https=args.use_https,
)
@vlogging.main_decorator
@operatornotify.main_decorator(subject='etiquette_flask_dev', notify_every_line=True)
def main(argv):
parser = argparse.ArgumentParser(
description='''