How to add a bunch of defaults to python/django logging - python

I would like to add in the following for every log entry:
user
request_method
status
The only way I've seen to be able to do this is a bit of a hackish way, which is to pass them as args, because that's picked up by the django formatter (I don't see any place where you can pass kwargs -- https://docs.python.org/3/library/logging.html#formatter-objects). Here's an example:
log.info("hi", {"user": str(request.user)})
And the following formatter:
'format': '{"message": "%(message)s", "extra": "%(args)s"}',
When I parse the log I do something like this:
info = {
'message': message
}
data = ast.literal_eval(extra)
for k,v in extra.items():
info[k] = v
It seems pretty ghetto -- is there a better way to do this, for example, a way to get the kwargs in the formatter? When I try doing something like:
log.info("hi", extra = {"user": str(request.user)})
It either errors, or doesn't pass in the args (obviously).

I would do it by linking the request to the current thread of execution. Here's an example of how to do this:
https://nedbatchelder.com/blog/201008/global_django_requests.html
Then you can use get_request() in your logging handler to get the request, from which you can get the user. (I'm not sure what you mean by "status")

Related

How to set cookies using GraphQLTestCase

I have dug far and deep but no working solution yet, based on some things I read from a thread somewhere, it seems you have to prefix headers with HTTP_ e.g. HTTP_AUTHORIZATION ends up being the Authorization header, based on that my current attempt is:
class TestImpersonatation(LogonUserMixin, GraphQLTestCase):
:
def test_...(self):
response = self.query(''' ...mutation here... ''',
headers = {
"HTTP_AUTHORIZATION": f"JWT {self.token}",
"HTTP_SET_COOKIE": f"my-jwt-token={self.token}"
},
variables={ ...vars... }
)
but alas, every time I get to my breakpoint there is only ever one cookie set, a csrf token. I am stumped but still trying... next it to se if a Client() instance can be passed in to work some magic instead of relying on the one created inside.

Loop over a WTForm class fields

How do I loop over a WTForms all filled with in a class
What I have tested is just using the loop around it
Forms classes
class someForm(FlaskForm):
some_filled_one = StringField('some_filled_one')
some_filled_two = StringField('some_filled_two')
...
then I have a anther place were I want to loop over this fields.
dict = {"some_filled_one" : "some text", "some_filled_two" : "some text 2"}
form = someForm()
for key in dict.keys():
response = request.form[key]
... #do some thing
this givs me an error :
werkzeug.exceptions.BadRequestKeyError
werkzeug.exceptions.HTTPException.wrap.<locals>.newcls: 400 Bad Request: The browser (or proxy) sent a request that this server could not understand.
Sorry, I don't have enough reputation to comment on this, so I have do draw an answer.
First, why do you create a dictionary for looping through request.form? Everything is accessable with request.form.keys(). And without sending a request before, the object request won't exist. So, looping trough the form objects you could either use request.form.keys() combined with request.form[key] or request.form.items()
Secondly, my guess is that whatever happens after #do some thing might be wrong.
Regards, Thomas

Post json to django but get 500 error

I'm writing an app to post json to Django, but get a 500 error in terminal like
[27/Jan/2015 20:50:38] "POST /datasave/ds/ HTTP/1.1" 500 10414
This is my jQuery code:
$(function() {
$('#upload').click(function() {
var json_obj = {
username: $('#username').val(),
password: $('#password').val(),
game_id1: '123',
csrfmiddlewaretoken: '{{ csrf_token}}'
};
$.post("http://192.168.0.109:8000/datasave/ds/", JSON.stringify(json_obj),
function(data) {
alert("OK");
},
"json");
})
})
And Django views code:
#csrf_exempt
def ds(request):
dicty = {}
if request.is_ajax:
if request.method == 'POST':
req = json.loads(request.body.decode("utf-8"))
obj, created =
Gamer.objects.update_or_create(
username=req.get(u'username', None),
password=req.get(u'password', None),
game_id1=req.get(u'game_id1', None))
print obj, created
dicty['username'] = req.get(u'username', None)
dicty['password'] = req.get(u'password', None)
dicty['create_at'] = str(timezone.now())
return JsonResponse(dicty)
As you are getting a 500 Internal server error one could assume that there is an issue in your view handler code. It's hard to say what's causing it really, so you should try to figure why it's happening. From looking at your code I can see two things that might cause this error.
First you are using dict as a variable name, which is probably a bad idea since dict is a built-in type in python.
The other possible cause could be the way your are accessing the req dict. You are accessing the keys username, password and game_id1. If any of these should be missing in the dict it will throw a KeyError exception. I like to access dicts using req.get('username', None) instead (replace None with another default value if you prefer. Another way to tackle that issue would be try/catch exception handling.
Also, depending on your Gamer model, trying to create a using an existing username (assuming you have unique=True) would probably throw an exception as well, so you should handle that as well (I think get_or_create could be handy here).
Generally when dealing with problems of this kind, use the inspector in your browser. It lets you see all the data sent and received during the request, which means (if you're running django in DEBUG mode) you'll also get to see the default stack trace page in the inspector. It should hold some valuable indication to what's causing the issue. Another option would be to write a small middleware or enable extended error logging to log errors to strout/stderr.

How do you use cookies and HTTP Basic Authentication in CherryPy?

I have a CherryPy web application that requires authentication. I have working HTTP Basic Authentication with a configuration that looks like this:
app_config = {
'/' : {
'tools.sessions.on': True,
'tools.sessions.name': 'zknsrv',
'tools.auth_basic.on': True,
'tools.auth_basic.realm': 'zknsrv',
'tools.auth_basic.checkpassword': checkpassword,
}
}
HTTP auth works great at this point. For example, this will give me the successful login message that I defined inside AuthTest:
curl http://realuser:realpass#localhost/AuthTest/
Since sessions are on, I can save cookies and examine the one that CherryPy sets:
curl --cookie-jar cookie.jar http://realuser:realpass#localhost/AuthTest/
The cookie.jar file will end up looking like this:
# Netscape HTTP Cookie File
# http://curl.haxx.se/rfc/cookie_spec.html
# This file was generated by libcurl! Edit at your own risk.
localhost FALSE / FALSE 1348640978 zknsrv 821aaad0ba34fd51f77b2452c7ae3c182237deb3
However, I'll get an HTTP 401 Not Authorized failure if I provide this session ID without the username and password, like this:
curl --cookie 'zknsrv=821aaad0ba34fd51f77b2452c7ae3c182237deb3' http://localhost/AuthTest
What am I missing?
Thanks very much for any help.
So, the short answer is you can do this, but you have to write your own CherryPy tool (a before_handler), and you must not enable Basic Authentication in the CherryPy config (that is, you shouldn't do anything like tools.auth.on or tools.auth.basic... etc) - you have to handle HTTP Basic Authentication yourself. The reason for this is that the built-in Basic Authentication stuff is apparently pretty primitive. If you protect something by enabling Basic Authentication like I did above, it will do that authentication check before it checks the session, and your cookies will do nothing.
My solution, in prose
Fortunately, even though CherryPy doesn't have a way to do both built-in, you can still use its built-in session code. You still have to write your own code for handling the Basic Authentication part, but in total this is not so bad, and using the session code is a big win because writing a custom session manager is a good way to introduce security bugs into your webapp.
I ended up being able to take a lot of things from a page on the CherryPy wiki called Simple authentication and access restrictions helpers. That code uses CP sessions, but rather than Basic Auth it uses a special page with a login form that submits ?username=USERNAME&password=PASSWORD. What I did is basically nothing more than changing the provided check_auth function from using the special login page to using the HTTP auth headers.
In general, you need a function you can add as a CherryPy tool - specifically a before_handler. (In the original code, this function was called check_auth(), but I renamed it to protect().) This function first tries to see if the cookies contain a (valid) session ID, and if that fails, it tries to see if there is HTTP auth information in the headers.
You then need a way to require authentication for a given page; I do this with require(), plus some conditions, which are just callables that return True. In my case, those conditions are zkn_admin(), and user_is() functions; if you have more complex needs, you might want to also look at member_of(), any_of(), and all_of() from the original code.
If you do it like that, you already have a way to log in - you just submit a valid session cookie or HTTPBA credentials to any URL you protect with the #require() decorator. All you need now is a way to log out.
(The original code instead has an AuthController class which contains login() and logout(), and you can use the whole AuthController object in your HTTP document tree by just putting auth = AuthController() inside your CherryPy root class, and get to it with a URL of e.g. http://example.com/auth/login and http://example.com/auth/logout. My code doesn't use an authcontroller object, just a few functions.)
Some notes about my code
Caveat: Because I wrote my own parser for HTTP auth headers, it only parses what I told it about, which means just HTTP Basic Auth - not, for example, Digest Auth or anything else. For my application that's fine; for yours, it may not be.
It assumes a few functions defined elsewhere in my code: user_verify() and user_is_admin()
I also use a debugprint() function which only prints output when a DEBUG variable is set, and I've left these calls in for clarity.
You can call it cherrypy.tools.WHATEVER (see the last line); I called it zkauth based on the name of my app. Take care NOT to call it auth, or the name of any other built-in tool, though .
You then have to enable cherrypy.tools.WHATEVER in your CherryPy configuration.
As you can see by all the TODO: messages, this code is still in a state of flux and not 100% tested against edge cases - sorry about that! It will still give you enough of an idea to go on, though, I hope.
My solution, in code
import base64
import re
import cherrypy
SESSION_KEY = '_zkn_username'
def protect(*args, **kwargs):
debugprint("Inside protect()...")
authenticated = False
conditions = cherrypy.request.config.get('auth.require', None)
debugprint("conditions: {}".format(conditions))
if conditions is not None:
# A condition is just a callable that returns true or false
try:
# TODO: I'm not sure if this is actually checking for a valid session?
# or if just any data here would work?
this_session = cherrypy.session[SESSION_KEY]
# check if there is an active session
# sessions are turned on so we just have to know if there is
# something inside of cherrypy.session[SESSION_KEY]:
cherrypy.session.regenerate()
# I can't actually tell if I need to do this myself or what
email = cherrypy.request.login = cherrypy.session[SESSION_KEY]
authenticated = True
debugprint("Authenticated with session: {}, for user: {}".format(
this_session, email))
except KeyError:
# If the session isn't set, it either wasn't present or wasn't valid.
# Now check if the request includes HTTPBA?
# FFR The auth header looks like: "AUTHORIZATION: Basic <base64shit>"
# TODO: cherrypy has got to handle this for me, right?
authheader = cherrypy.request.headers.get('AUTHORIZATION')
debugprint("Authheader: {}".format(authheader))
if authheader:
#b64data = re.sub("Basic ", "", cherrypy.request.headers.get('AUTHORIZATION'))
# TODO: what happens if you get an auth header that doesn't use basic auth?
b64data = re.sub("Basic ", "", authheader)
decodeddata = base64.b64decode(b64data.encode("ASCII"))
# TODO: test how this handles ':' characters in username/passphrase.
email,passphrase = decodeddata.decode().split(":", 1)
if user_verify(email, passphrase):
cherrypy.session.regenerate()
# This line of code is discussed in doc/sessions-and-auth.markdown
cherrypy.session[SESSION_KEY] = cherrypy.request.login = email
authenticated = True
else:
debugprint ("Attempted to log in with HTTBA username {} but failed.".format(
email))
else:
debugprint ("Auth header was not present.")
except:
debugprint ("Client has no valid session and did not provide HTTPBA credentials.")
debugprint ("TODO: ensure that if I have a failure inside the 'except KeyError'"
+ " section above, it doesn't get to this section... I'd want to"
+ " show a different error message if that happened.")
if authenticated:
for condition in conditions:
if not condition():
debugprint ("Authentication succeeded but authorization failed.")
raise cherrypy.HTTPError("403 Forbidden")
else:
raise cherrypy.HTTPError("401 Unauthorized")
cherrypy.tools.zkauth = cherrypy.Tool('before_handler', protect)
def require(*conditions):
"""A decorator that appends conditions to the auth.require config
variable."""
def decorate(f):
if not hasattr(f, '_cp_config'):
f._cp_config = dict()
if 'auth.require' not in f._cp_config:
f._cp_config['auth.require'] = []
f._cp_config['auth.require'].extend(conditions)
return f
return decorate
#### CONDITIONS
#
# Conditions are callables that return True
# if the user fulfills the conditions they define, False otherwise
#
# They can access the current user as cherrypy.request.login
# TODO: test this function with cookies, I want to make sure that cherrypy.request.login is
# set properly so that this function can use it.
def zkn_admin():
return lambda: user_is_admin(cherrypy.request.login)
def user_is(reqd_email):
return lambda: reqd_email == cherrypy.request.login
#### END CONDITIONS
def logout():
email = cherrypy.session.get(SESSION_KEY, None)
cherrypy.session[SESSION_KEY] = cherrypy.request.login = None
return "Logout successful"
Now all you have to do is enable both builtin sessions and your own cherrypy.tools.WHATEVER in your CherryPy configuration. Again, take care not to enable cherrypy.tools.auth. My configuration ended up looking like this:
config_root = {
'/' : {
'tools.zkauth.on': True,
'tools.sessions.on': True,
'tools.sessions.name': 'zknsrv',
}
}

Tornado handler thinks POST is missing argument when Firebug shows the argument being sent

I have a simple form using a POST method, consisting of a text box and a file. After hitting submit, I can see the post in Firebug as follows:
Parts multipart/form-data
posttext Some text
image BlahJFIFBlahExifBlahPhotoshopBlahBinaryStuff etc...
The Tornado handler that receives it looks like:
class NewPostHandler(BaseHandler, MessageMixin):
#tornado.web.authenticated
def post(self):
message = {
'posttext':self.get_argument('posttext'),
'image':self.get_argument('image'),
etc
But Tornado's handler returns:
[W 100618 23:07:32 web:775] 404 POST /a/message/new (127.0.0.1): Missing argument image
I'm not quite sure what I'm doing wrong here. Am I correct in thinking 'argument' means a input element's 'name' attribute? How can I make the handler see the argument?
Thanks for your help, I've been struggling with this for an hour and must admit I'm stumped!
For file uploads you should use self.request.files instead of self.get_argument().

Categories