I am relatively new to RESTful API, so it is certainly possible I am not designing this correctly.
I want to return a different subsets of a JSON user object from /api/users/[user_id] based on who is authenticating. So if the user "alice" is trying to access /api/users/alice, she would get much more of her info (such as private settings, etc) than user "bob" who would simply get her public profile.
I am currently using flask_restful with httpbasicauth. Right now I have the following:
class UserAPI(flask_restful.Resource):
#g.auth.login_required
def get(self, username):
# here is where I want to get the HTTPBasicAuth username
# to determine how much data to return
user = User.objects(username=username).exclude('password').first()
if user is not None:
return user.to_json()
else:
flask_restful.abort(404, message='User not found: ' + username)
The issue is that I cannot seem to figure out a CLEAN way to get the HTTP basic auth data. I know I could parse the request and decode the base-64 data, but I feel I shouldn't have to do that. Or, even better, find a way to pass the user_id from /api/users/[user_id] into the login_required annotation.
I feel this is would be a very common use case so I can't figure out why I can't find anything in this area. Am I designing this completely wrong?
Thanks very much!
I suggest not using flask.ext.httpauth. I didn't find it very useful. I use a decorator that takes the Authorization header and checks it with the db. You can access the username entered in request.authorization.username and the password is in request.authorization.password.
from flask import request
from flask.ext.restful import abort, Resource
from functools import wraps
def requires_auth(f):
#wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth:
abort(401)
user = User.objects(username=auth.username).first()
auth_ok = False
if user != None:
auth_ok = verify_password(auth.password) == user.password
if not auth_ok:
return abort(401)
return f(*args, **kwargs)
return decorated
class UserAPI(Resource):
#requires_auth
def get(self):
user = User.objects(username=request.authorization.username).\
exclude('password').first()
if user is not None:
return user.to_json()
else:
abort(404, message='User not found: ' + username)
Related
I'm creating a Restful API using Django Rest Framework, i'm not serving sensitive data but still i wanted to add some sort of authorization system for viewing my API endpoints.
Basically each user has an API key assigned, and in order to view any endpoint, the user needs to provide the key when performing any request. All the endpoints use only GET to retrieve the data, so what i did is the following:
The API key is provided in the GET params, so something like myURL/api/endpoint/?key=1234&filter=test
A middleware checks if that API key exists in my database, and if it does the user is able to get the data.
Here is my middleware:
TOKEN_QUERY = "key"
class TokenMiddleware(AuthenticationMiddleware):
def process_request(self, request):
if request.user.is_authenticated:
return None
else:
try:
token = request.GET[TOKEN_QUERY]
except Exception as e:
# A token isn't included in the query params
return JsonResponse({'error': 'Missing parameter: make sure to include your key.'})
try:
query = API_keys.objects.get(api_token=token)
except:
token = None
if token != None:
return None
else:
return JsonResponse({'error': 'Authentication failed. Make sure to provid a valid API key.'})
This system works without any problem, but i'm concerned about safety. How safe is this? Should i not use a GET request (of course i'll make sure to use HTTPS and SSL) ? Or is there a de facto way to create this kind of system? Any kind of advice is appreciated.
You can try this
from rest_framework import permissions
TOKEN_QUERY = "key"
# guest token validation class
class GuestTokenPermission(permissions.BasePermission):
def __init__(self, allowed_methods):
self.allowed_methods = allowed_methods
def has_permission(self, request, view):
token = request.META.get('HTTP_GUEST_TOKEN', None)
if token == TOKEN_QUERY:
return request.method in self.allowed_methods
else:
if request.user.is_superuser:
return request.method in self.allowed_methods
# put where you want to set permission
permission_classes = (partial(GuestTokenPermission, ['GET', 'POST', 'HEAD']),)
Refer https://www.django-rest-framework.org/api-guide/permissions/
I am using python flask to build a simple web app, in which user can hit a path say localhost:8000/ and login. if login is successful, another page is displayed, but i want to know how can I redirect to the main page, if the user is already logged in ? for example, if I log in for the first time, I am taken to the main page, and if I open a second tab and again hit the url for login, I am redirected to the main page automatically( much like gmail? ).
class LoginPage(object):
def on_get(self, req, resp, form={}):
For very simple applications HTTP Basic Auth is probably good enough. Flask makes this very easy. The following decorator applied around a function that is only available for certain users does exactly that:
from functools import wraps
from flask import request, Response
def check_auth(username, password):
"""This function is called to check if a username password combination is valid. """
return username == 'admin' and password == 'secret'
def authenticate():
"""Sends a 401 response that enables basic auth"""
return Response(
'Could not verify your access level for that URL.\n'
'You have to login with proper credentials', 401,
{'WWW-Authenticate': 'Basic realm="Login Required"'})
def requires_auth(f):
#wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
return authenticate()
return f(*args, **kwargs)
return decorated
To use this decorator, just wrap a view function:
#app.route('/secret-page')
#requires_auth
def secret_page():
return render_template('secret_page.html')
If you are using basic auth with mod_wsgi you will have to enable auth forwarding, otherwise apache consumes the required headers and does not send it to your application: WSGIPassAuthorization.
I have know that we created a session object with unique sessionID to response to client when a user first logged, and then when user request others' they will request with a cookie with that ID, so server can find the session object by that ID, which will denote the user have logged!
But this is one user situation, I find most blogs doesn't say if there are many users to manage, if we need to create many sessions in memory to every user. I think so!
But when I lookup flask-login source code, I can't find a session collections to maintain session for every user?
def login_user(user, remember=False, force=False, fresh=True):
'''
Logs a user in. You should pass the actual user object to this. If the
user's `is_active` property is ``False``, they will not be logged in
unless `force` is ``True``.
This will return ``True`` if the log in attempt succeeds, and ``False`` if
it fails (i.e. because the user is inactive).
:param user: The user object to log in.
:type user: object
:param remember: Whether to remember the user after their session expires.
Defaults to ``False``.
:type remember: bool
:param force: If the user is inactive, setting this to ``True`` will log
them in regardless. Defaults to ``False``.
:type force: bool
:param fresh: setting this to ``False`` will log in the user with a session
marked as not "fresh". Defaults to ``True``.
:type fresh: bool
'''
if not force and not user.is_active:
return False
user_id = getattr(user, current_app.login_manager.id_attribute)()
session['user_id'] = user_id
session['_fresh'] = fresh
session['_id'] = _create_identifier()
if remember:
session['remember'] = 'set'
_request_ctx_stack.top.user = user
user_logged_in.send(current_app._get_current_object(), user=_get_user())
return True
There is one session to keep the user, but what if another user come?
# -*- coding: utf-8 -*-
"""
flask.globals
~~~~~~~~~~~~~
Defines all the global objects that are proxies to the current
active context.
:copyright: (c) 2011 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
from functools import partial
from werkzeug.local import LocalStack, LocalProxy
def _lookup_req_object(name):
top = _request_ctx_stack.top
if top is None:
raise RuntimeError('working outside of request context')
return getattr(top, name)
def _lookup_app_object(name):
top = _app_ctx_stack.top
if top is None:
raise RuntimeError('working outside of application context')
return getattr(top, name)
def _find_app():
top = _app_ctx_stack.top
if top is None:
raise RuntimeError('working outside of application context')
return top.app
# context locals
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))
g = LocalProxy(partial(_lookup_app_object, 'g'))
I find session is an global variable, and is an localstack(), but I still don't konw how does it works?
class Local(object):
__slots__ = ('__storage__', '__ident_func__')
def __init__(self):
object.__setattr__(self, '__storage__', {})
object.__setattr__(self, '__ident_func__', get_ident)
def __iter__(self):
return iter(self.__storage__.items())
def __call__(self, proxy):
"""Create a proxy for a name."""
return LocalProxy(self, proxy)
def __release_local__(self):
self.__storage__.pop(self.__ident_func__(), None)
def __getattr__(self, name):
try:
return self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)
def __setattr__(self, name, value):
ident = self.__ident_func__()
storage = self.__storage__
try:
storage[ident][name] = value
except KeyError:
storage[ident] = {name: value}
def __delattr__(self, name):
try:
del self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)
Many people say it will use another thread id to identify, storage[ident][name] = value , but i disable threading, it works well for multi-users?
I just find it use current_user variable to identify current user, but current_user is so magic! It doesn't maintain user session collection but just one current_user to solve the problem! I don't know how it works?
def login_required(func):
'''
If you decorate a view with this, it will ensure that the current user is
logged in and authenticated before calling the actual view. (If they are
not, it calls the :attr:`LoginManager.unauthorized` callback.) For
example::
#app.route('/post')
#login_required
def post():
pass
If there are only certain times you need to require that your user is
logged in, you can do so with::
if not current_user.is_authenticated:
return current_app.login_manager.unauthorized()
...which is essentially the code that this function adds to your views.
It can be convenient to globally turn off authentication when unit testing.
To enable this, if the application configuration variable `LOGIN_DISABLED`
is set to `True`, this decorator will be ignored.
.. Note ::
Per `W3 guidelines for CORS preflight requests
<http://www.w3.org/TR/cors/#cross-origin-request-with-preflight-0>`_,
HTTP ``OPTIONS`` requests are exempt from login checks.
:param func: The view function to decorate.
:type func: function
'''
#wraps(func)
def decorated_view(*args, **kwargs):
if request.method in EXEMPT_METHODS:
return func(*args, **kwargs)
elif current_app.login_manager._login_disabled:
return func(*args, **kwargs)
elif not current_user.is_authenticated:
return current_app.login_manager.unauthorized()
return func(*args, **kwargs)
return decorated_view
So where is process of comparing current user sessionID from cookie with session collection mantained by server? Anybody can help me?
I take a look at flask-login/flask_login/login_manager.py:_load_user()
I guess you are talking about SESSION_PROTECTION. In this case the way user will be reloaded depends on basic or strong auth modes. If you have no session protection, flask try to load the user from request, header or cookies if you have handlers for this.
class LoginManager(object):
...
def _load_user(self):
'''Loads user from session or remember_me cookie as applicable'''
user_accessed.send(current_app._get_current_object())
# first check SESSION_PROTECTION
config = current_app.config
if config.get('SESSION_PROTECTION', self.session_protection):
deleted = self._session_protection()
if deleted:
return self.reload_user()
# If a remember cookie is set, and the session is not, move the
# cookie user ID to the session.
#
# However, the session may have been set if the user has been
# logged out on this request, 'remember' would be set to clear,
# so we should check for that and not restore the session.
is_missing_user_id = 'user_id' not in session
if is_missing_user_id:
cookie_name = config.get('REMEMBER_COOKIE_NAME', COOKIE_NAME)
header_name = config.get('AUTH_HEADER_NAME', AUTH_HEADER_NAME)
has_cookie = (cookie_name in request.cookies and
session.get('remember') != 'clear')
if has_cookie:
return self._load_from_cookie(request.cookies[cookie_name])
elif self.request_callback:
return self._load_from_request(request)
elif header_name in request.headers:
return self._load_from_header(request.headers[header_name])
return self.reload_user()
def _load_from_request(self, request):
user = None
if self.request_callback:
user = self.request_callback(request)
if user is not None:
self.reload_user(user=user)
app = current_app._get_current_object()
user_loaded_from_request.send(app, user=_get_user())
else:
self.reload_user()
Flask passes request to your callback if it presented. Flask-login has good example(Custom Login using Request Loader) how you can load user from request.
#login_manager.request_loader
def load_user_from_request(request):
# first, try to login using the api_key url arg
api_key = request.args.get('api_key')
if api_key:
user = User.query.filter_by(api_key=api_key).first()
if user:
return user
# next, try to login using Basic Auth
api_key = request.headers.get('Authorization')
if api_key:
api_key = api_key.replace('Basic ', '', 1)
try:
api_key = base64.b64decode(api_key)
except TypeError:
pass
user = User.query.filter_by(api_key=api_key).first()
if user:
return user
# finally, return None if both methods did not login the user
return None
The api_key can be assigned when a client will be authorized through the backend for different logins from one physical machine.
I am doing APIs, and trying to use token. I want to achieve:
1, client issues a request to server with token in the header;
2, server verify the token and do something (one time).
Looking into the DRF, If I would like to overwrite the authenticate function, I also need to return User object or our own customized User object. I would not like to return any User object, because the whole process does not involve any User, just token and permission to do something. How to do this?
Thanks
First of all, create modelToken in models.py. Furthermore, you need to create a token_required decorator. Whenever a user logs in, a token is created, and whenever she/he logs out, the token is destroyed.
login:
def login(request):
username=request.payload.get('username')
password=request.payload.get('password')
user,err=Auth.authenticate(username,password)
if err:
raise Exception()
token=Token.generate()
#you can return user
return {'token':token}
decorators:
def token_required(func):
def inner(request, *args, **kwargs):
try:
request.token=Token.objects.get(token=token)
return func(request, *args, **kwargs)
except Token.DoesNotExists:
pass
return inner
logout:
#token_required
def logout(request):
if request.method=='POST':
request.token.delete()
return {'status':'ok'}
I am connecting to several APIs (e.g. Twitter, GitHub, etc.) using Flask-oauthlib. Currently, I have each of these services as a separate blueprint. Within the view files for each of the services, there are the same three views: login, authorized, and get_token. The code right now is not very DRY, but I am struggling to understand how to centralize these views (more conceptually).
How could I make this more DRY? I would like to understand more conceptually rather than someone actually writing the code for me.
Below are a few items that may be helpful. This is the application structure:
- App
- Services
- FourSquare BP
- GitHub BP
- Twitter BP
- ...
- Other BPs
The generic API view would likely go under Services/api_views.py
Here is an example of one of the API Blueprint view files (Twitter).
twitter = Blueprint('twitter', __name__, url_prefix='/twitter')
bp = twitter
bp.api = TwitterAPI()
bp.oauth = bp.api.oauth_app
# Below here is the exact same for each file.
#bp.route('/')
#login_required
def login():
if current_user.get(bp.name, None):
return redirect(url_for('frontend.index'))
return bp.oauth.authorize(callback=url_for('.authorized', _external=True))
#bp.route('/authorized')
#bp.oauth.authorized_handler
def authorized(resp):
if resp is None:
flash(u'You denied the request to sign in.')
return redirect(url_for('frontend.index'))
if bp.oauth_type == 'oauth2':
resp['access_token'] = (resp['access_token'], '')
current_user[bp.name] = resp
current_user.save()
flash('You were signed in to %s' % bp.name.capitalize())
return redirect(url_for('frontend.index'))
#bp.oauth.tokengetter
def get_token(token=None):
if bp.oauth_type == 'oauth2':
return current_user.get(bp.name, None)['access_token']
return current_user.get(bp.name, None)['oauth_token']
I tried just placing the views together in a class then importing those, but was having trouble with the various decorators (the oauth decorators were giving the most trouble).
I figured out something that seems like a good solution to the above issue. Please let me know if this is in fact a good solution.
I decided to use the Flask class-based views. This got me most of the way to just create a class-view for each of the main API functions above:
class APILoginView(View):
decorators = [login_required]
def __init__(self, blueprint):
self.blueprint = blueprint
def dispatch_request(self):
if current_user.get(self.blueprint.name, None):
return redirect(url_for('frontend.index'))
return self.blueprint.oauth.authorize(callback=url_for('.authorized', _external=True))
class APIAuthorizedView(View):
decorators = [login_required]
def __init__(self, blueprint):
self.blueprint = blueprint
def dispatch_request(self, resp):
if resp is None:
flash(u'You denied the request to sign in.')
return redirect(url_for('frontend.index'))
if self.blueprint.api.oauth_type == 'oauth2':
resp['access_token'] = (resp['access_token'], '') #need to make it a tuple for oauth2 requests
current_user[self.blueprint.name] = resp
current_user.save()
flash('You were signed in to %s' % self.blueprint.name.capitalize())
return redirect(url_for('frontend.index'))
That was the easier part. The trickier thing was figuring out how to generalize the tokengetter, required for the Flask-OAuthLib library. Here is what I ended up with:
class APIToken():
def __init__(self, blueprint):
self.blueprint = blueprint
def get_token(self, token=None):
if self.blueprint.api.oauth_type == 'oauth2':
return current_user.get(self.blueprint.name, None)['access_token']
return current_user.get(self.blueprint.name, None)['oauth_token']
And finally, create a function to register those views on a blueprint:
def registerAPIViews(blueprint):
login_view = APILoginView.as_view('login', blueprint=blueprint)
auth_view = blueprint.oauth.authorized_handler(
APIAuthorizedView.as_view('authorized', blueprint=blueprint))
blueprint.add_url_rule('/', view_func=login_view)
blueprint.add_url_rule('/authorized', view_func=auth_view)
apiToken = APIToken(blueprint)
token_getter = blueprint.oauth.tokengetter(apiToken.get_token)
return blueprint