[英]Verify Basic Auth has the same Username as in URL [Flask]
Basically I am doing a hybrid of sending the username in the url and in Basic Auth when I need the user to be authenticated.基本上,当我需要对用户进行身份验证时,我正在混合在 url 和基本身份验证中发送用户名。
My full code is:我的完整代码是:
from flask import Flask, request, send_from_directory, g
from flask_restful import Resource, Api
from flask_httpauth import HTTPBasicAuth
from sqlalchemy import create_engine, Column, Integer, String, Boolean
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from passlib.apps import custom_app_context as pwd_context
import pathlib
import shutil
import os
SUCCESS = 200
UNAUTHORIZED = 401
BAD_REQUEST = 402
INTERNAL_ERROR = 500
INVALID_MEDIA = 415
db_engine = create_engine('mysql://root:mypasswordisembarrassing@localhost/store', echo=True)
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
username = Column(String(16), index=True, unique=True, nullable=False)
password_hash = Column(String(128), nullable=False)
avatar = Column(Boolean, default=False, nullable=False)
def hash_password(self, password):
self.password_hash = pwd_context.encrypt(password)
def verify_password(self, password):
return pwd_context.verify(password, self.password_hash)
def __repr__(self):
return "<User(username='%s', password='%s', avatar path='%s')>" % (
self.username, self.password_hash, self.avatar)
Base.metadata.create_all(db_engine)
Session = sessionmaker(bind=db_engine)
session = Session()
app = Flask(__name__)
app.secret_key = 'the quick brown fox jumps over the lazy dog'
api = Api(app) #blueprint?
auth = HTTPBasicAuth()
@auth.verify_password
def verify_password(username, password):
u = session.query(User).filter_by(username=username).first()
if not u or not u.verify_password(password):
return False
g.user = u
return True
class Avatar(Resource):
def get(self, name):
u = session.query(User).filter_by(username=name).first()
assert u.avatar is not None
if u.avatar:
send_from_directory('static/u/%s/avatar.png', name) #Aparently Flask should not do this? Apache...
else:
send_from_directory('static/defaults/u/avatar.png')
return "Success", SUCCESS
@auth.login_required
def post(self, name):
u = g.user
if 'avatar' not in request.files:
return "No file recieved", BAD_REQUEST
file = request.files['avatar']
if file.content_type != 'image/png':
return "Avatar must be a png", INVALID_MEDIA
assert pathlib.Path("static").exists()
pathlib.Path("static/u/%s" % u.username).mkdir(parents=True, exist_ok=True)
file.save('static/u/%s/avatar.png' % u.username)
u.avatar = True
session.commit()
return "Success", SUCCESS
class Player(Resource):
def post(self, name):
password = request.json.get('password')
if not valid_password(password):
return "Password needs to be longer than 5 characters", BAD_REQUEST
u = User(username=name)
u.hash_password(password)
session.add(u)
session.commit()
return "Success", SUCCESS
@auth.login_required
def delete(self, name):
doomed_user = g.user
shutil.rmtree('static/u/%s' % doomed_user.username)
session.delete(doomed_user)
session.commit()
return "Success", SUCCESS
def valid_password(password):
return len(password) >= 6
api.add_resource(Avatar, '/u/<string:name>/avatar.png')
api.add_resource(Player, '/u/<string:name>')
if __name__ == '__main__':
app.run()
I am concerned about this part:我很关心这部分:
class Avatar(Resource):
def get(self, name):
u = session.query(User).filter_by(username=name).first()
assert u.avatar is not None
if u.avatar:
send_from_directory('static/u/%s/avatar.png', name) #Aparently Flask should not do this? Apache...
else:
send_from_directory('static/defaults/u/avatar.png')
return "Success", SUCCESS
@auth.login_required
def post(self, name):
u = g.user
if 'avatar' not in request.files:
return "No file recieved", BAD_REQUEST
file = request.files['avatar']
if file.content_type != 'image/png':
return "Avatar must be a png", INVALID_MEDIA
assert pathlib.Path("static").exists()
pathlib.Path("static/u/%s" % u.username).mkdir(parents=True, exist_ok=True)
file.save('static/u/%s/avatar.png' % u.username)
u.avatar = True
session.commit()
return "Success", SUCCESS
Where I would like to use the variable name when doing authorizing but if someone sends a request to someone else's url with their credentials they could change other user data, so for now I'm just using the authorized username in those functions.我想在授权时使用变量名,但如果有人用他们的凭据向其他人的 url 发送请求,他们可以更改其他用户数据,所以现在我只是在这些功能中使用授权用户名。
For example: url/u/John -uBill:password should be marked invalid例如: url/u/John -uBill:password 应标记为无效
Is there any neat way I can have the @login_required
decorator check if the url is also valid?有没有什么巧妙的方法可以让@login_required
装饰器检查 url 是否也有效? I would rather not do if name != u.username: ...
at the beginning of every protected route. if name != u.username: ...
在每个受保护路由的开头,我宁愿不这样做。
You have two options.你有两个选择。
If you need to do this for every route, then you can add the username check to your verify_password
callback function:如果您需要对每条路由都执行此操作,那么您可以将用户名检查添加到您的verify_password
回调函数中:
@auth.verify_password
def verify_password(username, password):
if username != request.view_args.get('name'):
return False
u = session.query(User).filter_by(username=username).first()
if not u or not u.verify_password(password):
return False
g.user = u
return True
If you need to add this check to a subset of your routes, then you can do this in a separate decorator, which would be something like this:如果您需要将此检查添加到您的路由子集,那么您可以在单独的装饰器中执行此操作,这将是这样的:
from flask import request, abort
from functools import wraps
def check_username(f):
@wraps(f)
def wrapped(*args, **kwargs):
if auth.username != request.view_args['name']:
abort(401)
return f(*args, **kwargs)
Then you can add the decorator to any routes that need it:然后你可以将装饰器添加到任何需要它的路由中:
class Avatar(Resource):
# ...
@auth.login_required
@check_username
def post(self, name):
# ...
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.