Commit 7df9163c authored by iv's avatar iv

Change self.authorization checked for every method by a verification in before_request.

parent 7feb2668
from flask import Flask, request, redirect, url_for, render_template, make_response from flask import Flask, request, redirect, url_for, render_template, make_response, g
from flask.views import MethodView from flask.views import MethodView
from string import atoi from string import atoi
import shutil import shutil
...@@ -32,17 +32,43 @@ def is_authorized(cookies_list): ...@@ -32,17 +32,43 @@ def is_authorized(cookies_list):
FS_HANDLER = utils.FilesystemHandler(FS_PATH, URI_BEGINNING_PATH['webdav']) FS_HANDLER = utils.FilesystemHandler(FS_PATH, URI_BEGINNING_PATH['webdav'])
@app.before_request
def before_request():
"""
allow cross origin for webdav uri that are authorized
and filter unauthorized requests!
"""
if request.path.startswith(URI_BEGINNING_PATH['webdav']):
response = None
headers = {}
headers['Access-Control-Max-Age'] = '3600'
headers['Access-Control-Allow-Credentials'] = 'true'
content = ''
if is_authorized(request.cookies):
headers['Access-Control-Allow-Origin'] = request.headers.get('Origin', '*')
headers['Access-Control-Allow-Headers'] = 'Origin, Accept, Accept-Encoding, Content-Length, Content-Type, Authorization, Depth, If-Modified-Since, If-None-Match'
headers['Access-Control-Expose-Headers'] = 'Content-Type, Last-Modified, WWW-Authenticate'
response = make_response(content, 200)
response.headers = headers
else:
headers['WWW-Authenticate'] = 'Nayookie login_url=' + request.url_root + URI_BEGINNING_PATH['authorization'] + '{?back_url}'
response = make_response(content, 401)
response.headers = headers
# do not handle the request if not authorized
return response
g.response = response
class WebDAV(MethodView): class WebDAV(MethodView):
methods = ['GET', 'HEAD', 'PUT', 'PROPFIND', 'PROPPATCH', 'MKCOL', 'DELETE', 'COPY', 'MOVE'] methods = ['GET', 'HEAD', 'PUT', 'PROPFIND', 'PROPPATCH', 'MKCOL', 'DELETE', 'COPY', 'MOVE']
def __init__(self): def __init__(self):
self.authorization = is_authorized(request.cookies)
self.baseuri = URI_BEGINNING_PATH['webdav'] self.baseuri = URI_BEGINNING_PATH['webdav']
def before_request(self, pathname):
pass
def get_body(self): def get_body(self):
""" get the request's body """
request_data = request.data request_data = request.data
if not request_data and atoi(request.headers['Content-length']): if not request_data and atoi(request.headers['Content-length']):
try: try:
...@@ -53,52 +79,38 @@ class WebDAV(MethodView): ...@@ -53,52 +79,38 @@ class WebDAV(MethodView):
return request_data return request_data
def head(self, pathname): def head(self, pathname):
""" HEAD: returns headers only """ """
origin = request.headers.get('Origin', '*') HEAD:
returns headers only
response = None """
headers = {}
headers['Access-Control-Max-Age'] = '3600'
headers['Access-Control-Allow-Credentials'] = 'true'
content = ''
if self.authorization:
headers['Access-Control-Allow-Origin'] = origin
headers['Access-Control-Allow-Headers'] = 'Origin, Accept, Accept-Encoding, Content-Length, Content-Type, Authorization, Depth, If-Modified-Since, If-None-Match'
headers['Access-Control-Expose-Headers'] = 'Content-Type, Last-Modified, WWW-Authenticate'
response = make_response(content, 200)
else:
headers['WWW-Authenticate'] = 'Nayookie login_url=' + request.url_root + URI_BEGINNING_PATH['authorization'] + '{?back_url}'
response = make_response(content, 401)
response.headers = headers return g.response
return response
def get(self, pathname): def get(self, pathname):
""" GET: return headers + body (resource content or list of resources) """ """
GET:
return headers + body (resource content or list of resources)
"""
response = g.response
localpath = FS_HANDLER.uri2local(pathname) localpath = FS_HANDLER.uri2local(pathname)
response = self.head(pathname)
# TODO if into a collection => list of the ressources # TODO if into a collection => list of the ressources
print('localpath: ' + localpath)
data = '' data = ''
if self.authorization: if os.path.isdir(localpath):
if os.path.isdir(localpath): data = "\n".join(FS_HANDLER.get_children(pathname))
data = "\n".join(FS_HANDLER.get_children(pathname)) elif os.path.isfile(localpath):
elif os.path.isfile(localpath): try:
try: data_resource = FS_HANDLER.get_data(pathname)
data_resource = FS_HANDLER.get_data(pathname) # TODO send large response by chunks would be nice for big
# TODO send large response by chunks would be nice for big # files... http://flask.pocoo.org/docs/0.10/patterns/streaming/
# files... http://flask.pocoo.org/docs/0.10/patterns/streaming/ data = data_resource.read()
data = data_resource.read() except:
except: # 403?
  • J'ai l'impression qu'un 5xx serait plus approprié ici: il s'agit d'un problème de droits entre le serveur et le FS, pas entre le client et le serveur.

Please register or sign in to reply
# 403? response.status = '403'
response.status = '403' else:
else: response.status = '404'
response.status = '404'
response.data = data response.data = data
return response return response
...@@ -108,32 +120,34 @@ class WebDAV(MethodView): ...@@ -108,32 +120,34 @@ class WebDAV(MethodView):
on collection: 405 Method Not Allowed, on collection: 405 Method Not Allowed,
on ressource: create if not existschange content on ressource: create if not existschange content
""" """
response = self.head(pathname)
response = g.response
if self.authorization:
localpath = FS_HANDLER.uri2local(pathname) localpath = FS_HANDLER.uri2local(pathname)
request_body = self.get_body() request_body = self.get_body()
  • Ajouter un TODO ici, symétrique du cat GET: éviter de charger tout le body de la request en mémoire en une fois, mais l'écrire dans le fichier par morceaux.

Please register or sign in to reply
if request_body is None: if request_body is None:
response.status = '500' response.status = '500'
elif os.path.isdir(localpath): elif os.path.isdir(localpath):
response.status = '405' response.status = '405'
else: else:
response.status = str(FS_HANDLER.put(pathname, request_body)) response.status = str(FS_HANDLER.put(pathname, request_body))
return response return response
def propfind(self, pathname): def propfind(self, pathname):
response = self.head(pathname)
if self.authorization: response = g.response
# currently unsupported
response.status = '423' # currently unsupported
response.status = '423'
return response return response
def proppatch(self, pathname): def proppatch(self, pathname):
response = self.head(pathname)
if self.authorization: response = g.response
# currently unsupported
response.status = '423' # currently unsupported
response.status = '423'
return response return response
def mkcol(self, pathname): def mkcol(self, pathname):
...@@ -142,9 +156,9 @@ class WebDAV(MethodView): ...@@ -142,9 +156,9 @@ class WebDAV(MethodView):
creates a collection (that corresponds to a directory on the file system) creates a collection (that corresponds to a directory on the file system)
""" """
response = self.head(pathname) response = g.response
if self.authorization:
response.status = str(FS_HANDLER.mkcol(pathname)) response.status = str(FS_HANDLER.mkcol(pathname))
return response return response
def delete(self, pathname): def delete(self, pathname):
...@@ -153,23 +167,23 @@ class WebDAV(MethodView): ...@@ -153,23 +167,23 @@ class WebDAV(MethodView):
delete a resource or collection delete a resource or collection
""" """
response = self.head(pathname) response = g.response
if self.authorization:
localpath = FS_HANDLER.uri2local(pathname) localpath = FS_HANDLER.uri2local(pathname)
if not os.path.exists(localpath): if not os.path.exists(localpath):
response.status = '404' response.status = '404'
if os.path.isdir(localpath): if os.path.isdir(localpath):
  • Juste une idée: demander un token à usage unique (donc avec tracking server-side des tokens émis, sans persistence) pour confirmer une suppression ?

Please register or sign in to reply
try: try:
shutil.rmtree(localpath) shutil.rmtree(localpath)
response.status = '204' response.status = '204'
except OSError: except OSError:
response.status = '403' response.status = '403'
elif os.path.isfile(localpath): elif os.path.isfile(localpath):
try: try:
os.remove(localpath) os.remove(localpath)
response.status = '204' response.status = '204'
except OSError: except OSError:
response.status = '403' response.status = '403'
return response return response
def copy(self, pathname): def copy(self, pathname):
...@@ -178,38 +192,37 @@ class WebDAV(MethodView): ...@@ -178,38 +192,37 @@ class WebDAV(MethodView):
copy a resource or collection copy a resource or collection
""" """
response = self.head(pathname) response = g.response
if self.authorization:
localpath = FS_HANDLER.uri2local(pathname) localpath = FS_HANDLER.uri2local(pathname)
destination = request.headers['Destination'] destination = request.headers['Destination']
host = request.headers['Host'] host = request.headers['Host']
destination = destination.split(host + URI_BEGINNING_PATH['webdav'], 1)[-1] destination = destination.split(host + URI_BEGINNING_PATH['webdav'], 1)[-1]
destination_path = FS_HANDLER.uri2local(destination) destination_path = FS_HANDLER.uri2local(destination)
print('COPY: %s -> %s' % (localpath, destination_path)) print('COPY: %s -> %s' % (localpath, destination_path))
if not os.path.exists(localpath): if not os.path.exists(localpath):
response.status = '404' response.status = '404'
elif not destination_path: elif not destination_path:
response.status = '400' response.status = '400'
elif 'Overwrite' in request.headers and request.headers['Overwrite'] == 'F' and os.path.exists(destination_path): elif 'Overwrite' in request.headers and request.headers['Overwrite'] == 'F' and os.path.exists(destination_path):
response.status = '412' response.status = '412'
else:
response.status = '201'
if os.path.exists(destination_path):
delete_response = self.delete(destination)
response.status = '204'
if os.path.isfile(localpath):
try:
shutil.copy2(localpath, destination_path)
except:
print('problem with copy2')
else: else:
response.status = '201' try:
if os.path.exists(destination_path): shutil.copytree(localpath, destination_path)
delete_response = self.delete(destination) except:
print delete_response.status print('problem with copytree')
response.status = '204'
if os.path.isfile(localpath):
try:
shutil.copy2(localpath, destination_path)
except:
print('problem with copy2')
else:
try:
shutil.copytree(localpath, destination_path)
except:
print('problem with copytree')
return response return response
def move(self, pathname): def move(self, pathname):
...@@ -218,28 +231,30 @@ class WebDAV(MethodView): ...@@ -218,28 +231,30 @@ class WebDAV(MethodView):
move a resource or collection move a resource or collection
""" """
response = self.head(pathname) response = g.response
if self.authorization:
copy_response = self.copy(pathname) copy_response = self.copy(pathname)
response.status = copy_response.status response.status = copy_response.status
if copy_response.status == '201' or copy_response.status == '204': if copy_response.status == '201' or copy_response.status == '204':
delete_response = self.delete(pathname) delete_response = self.delete(pathname)
if delete_response.status != '204': if delete_response.status != '204':
response.status = '424' response.status = '424'
return response return response
app.add_url_rule(URI_BEGINNING_PATH['webdav'] + '<path:pathname>', view_func=WebDAV.as_view('dav')) app.add_url_rule(URI_BEGINNING_PATH['webdav'] + '<path:pathname>', view_func=WebDAV.as_view('dav'))
@app.route(URI_BEGINNING_PATH['authorization']) @app.route(URI_BEGINNING_PATH['authorization'], methods=['GET', 'POST'])
def authorize(): def authorize():
origin = request.headers.get('Origin') if request.method == 'POST':
print origin response = make_response(render_template('authorization_page_cookie_set.html', headers=headers, origin=origin, back_url=back_url))
  • Véfier qu'il n'y a pas de Origin dans le post ? Sinon je pense que le JS pourrait directement faire ça au lieu de l'utilisateur.

Please register or sign in to reply
headers = request.headers response.set_cookie('mycookie', value='', max_age=None, expires=None, path='/',
back_url = request.args.get('back_url') domain=None, secure=None, httponly=False)
print origin else:
response = make_response(render_template('authorization_page.html', headers=headers, origin=origin, back_url=back_url)) origin = request.headers.get('Origin')
response.set_cookie('mycookie', value='', max_age=None, expires=None, path='/', domain=None, secure=None, httponly=False) headers = request.headers
back_url = request.args.get('back_url')
response = make_response(render_template('authorization_page.html', headers=headers, origin=origin, back_url=back_url))
return response return response
@app.route(URI_BEGINNING_PATH['editor']) @app.route(URI_BEGINNING_PATH['editor'])
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment