I'm writing a python function to validate a token from an email. In the email, there is a url with the endpoint of it. I have two url parameters, the token and email address. In my endpoint I have to check :
if the parameters are in the URL
if there is a associate token in the database
if it corresponds to the user email
if the token is still valid (expires after 2 days)
if it has already been used
I choose to wrap all those checks in a try except block, I will always return the same error "invalid token" so I don't have to precisely check individual error. I used the function assertFalse and assertEqual that will raise an exception if it's not correct.
try:
# pull from url
email = request.GET['email']
value_token = request.GET['token']
# test if valid
token = EmailValidationToken.objects.get(token=value_token)
assertFalse(token.consumed)
assertEqual(email, token.user.email)
assertFalse(token.is_expired())
except:
pass # return error
I like the way I did it because it's super clean.
Is it a good practice ? Is there other solution for this problem ?
No, using assert for control flow rather than debugging is poor practice, because assertions can be turned off. Just use an ordinary if statement.
# pull from url
email = request.GET['email']
value_token = request.GET['token']
# test if valid
token = EmailValidationToken.objects.get(token=value_token)
if token.consumed or email != token.user.email or token.is_expired():
pass # return error
If you absolutely insist on controlling your program's flow by raising an error (which is a valid thing to do in some cases), do so with raise, e.g. if condition: raise TypeError.
Related
I was wondering how to validate that a user has correctly entered their spotify developer profile information correctly. I have a file, credentials.py, with 3 constants needed to get a spotify API token. In main.py, I use these constants like so.
def authenticate_user():
if not credentials.SPOTIPY_CLIENT_SECRET or not credentials.SPOTIPY_CLIENT_ID:
raise RuntimeError("Spotify credentials have not been set! Check credentials.py")
os.environ["SPOTIPY_CLIENT_ID"] = credentials.SPOTIPY_CLIENT_ID
os.environ["SPOTIPY_CLIENT_SECRET"] = credentials.SPOTIPY_CLIENT_SECRET
os.environ["SPOTIPY_REDIRECT_URI"] = credentials.SPOTIPY_REDIRECT_URI
sp = spotipy.Spotify(auth_manager=spotipy.SpotifyOAuth())
return sp
The resulting variable, sp, does not seem to have any methods to verify that it has successfully been validated. On attempting to use any methods, you are sent to a webpage with the corresponding spotify endpoint, showing that the request failed. When exiting the page, no error is thrown and the program hangs. How do I throw an error on invalid information being used to authenticate with the spotify servers?
You can check if the spotipy api returns a boolean upon verification, just make an if statement that looks like this:
If (sp == True):
Print('credentials valid')
Base on Spotify docs here
you should get a Spotify.oauth2.SpotifyOauthError
then you can surround the code with a try and except.
you can do it like this:
try:
sp = spotipy.Spotify(auth_manager=spotipy.SpotifyOAuth())
return sp
except SpotifyOauthError as e:
raise RuntimeError(f"Spotify credentials didn't work, error={e}")
just don't forget to import the exception from Spotify.oauth2.SpotifyOauthError
My use case is to generate token for reset password api. Which I am doing with itsdangerous library in python.
https://pythonhosted.org/itsdangerous/.
This token(within reset password link) is forwarder through email to client, the token has expiry time limit which is validated and after that password reset can go through successfully.
Issue here is that once password reset is successful how do I make sure the same token(email link) cannot be used again within the expiry time limit. I can see itsdangerous has URLSafeTimedSerializer which helps evaluate during the validation phase on how old the token is. On the other hand TimedJSONWebSignatureSerializer helps set the expiry time while generating token. Please check the attached piece of code.
Is there a better way to expire token forcefully? If not what would be the best way to save the state of token that it has been used?
import itsdangerous
key = "test"
# signer = itsdangerous.URLSafeTimedSerializer(key)
signer = itsdangerous.TimedJSONWebSignatureSerializer(key, expires_in=5)
email = "email#test.com"
# token = email # to be used with URLSafeTimedSerializer
token = signer.dumps({"email": email})
print token
# print signer.loads(token, max_age=5) # to be used with URLSafeTimedSerializer
print str(signer.loads(token)["email"]) # to be used with TimedJSONWebSignatureSerializer
Once your token is generated and signed it remains valid until it expires. You cannot change that anymore. With that in mind, it also means that you cannot change any of its payload once it was signed, otherwise it will be rendered invalid (due to an invalid signature).
One thing you could do however is to generate an unique key ("some_key") once you generate your token and store the key in your database. In the end, the tokens payload which will be issued to the user could look like this: {"email": email, "reset_key": "some_key"}.
Each time someone would try to reset his password, you would simply verify that key first in order to allow or reject a request.
Once the reset was successfull you would simply remove that key from your database (or flag it as invalid). That would make the following requests containing the same token invalid, even though the token itself is still valid from an expiry perspective.
I hope that helps!
I realise this is a late answer, so added for the benefit of others finding this question.
Another approach might be to use a boolean session variable such as tokenused and set this to True once the token has been de-serialised; thus invalidating the use of the token.
For example, using the session object in Flask:
uid = {}
try:
if not session['tokenused']:
session['tokenused'] = True
s = Serializer(app.config['SECRET_KEY'])
uid = s.loads(token)
except Exception as err:
errors.internal_server_error(err)
return uid
I have 2 Gmail accounts, and I use one ("gmail1") to forward all e-mail to the other one ("gmail2"). This works fine, but I discovered recently (after years!) that in fact, Gmail does not forward all my e-mail, but only the e-mail it considers not to be spam. Therefore, checking my spam folder in gmail2 for missing e-mails, I have often blamed senders, when really the e-mail had gotten lost on gmail1. I want to solve this programatically, by regularly checking for spam e-mails in gmail1 and importing them into gmail2 (then I'm fine with gmail2 categorizing as spam or not, as long as the mail makes its way there).
I'm only a very amateur programmer, and so far thanks to some samples from the gmail API docs, I've managed to log in to my accounts, and retrieve some messages corresponding to a query, including for example recent spam e-mails. However, I'm struggling with the "import" concept and how to use it. There are no Python examples for this, and I couldn't solve the problems I'm facing.
Where I am right now:
- if I "get" a message from gmail1 and attempt an import_ call on gmail2, using the message I just retrieved, I get an error because threadId is not allowed
- if I do the same and then "del message['threadId']" then the error becomes error 400 : 'raw' RFC822 payload message string or uploading message via /upload/* URL required. I've seen that there are some situations where upload is required, but I am completely lost as to what I should do to make this work.
Here's what I have so far (sorry for the very hacky style):
# skipping imports
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly','https://www.googleapis.com/auth/gmail.modify']
def getMessage(service, user_id, msg_id):
"""from gmail API examples"""
try:
message = service.users().messages().get(userId=user_id, id=msg_id).execute()
return message
except errors.HttpError as error:
print ('An error occurred:' , error)
def listMessagesMatchingQuery(service, user_id, query=''):
"""from gmail API examples"""
# skipping some code
return messages
def login(accountId):
"""from gmail API examples, adapted to handle 2 accounts"""
# skipping some code
return build('gmail', 'v1', credentials=creds)
def importMessage(service, user_id, msg):
"""my daring attempt at using import, without any Python sample to use as a basis"""
try:
message = service.users().messages().import_(userId=user_id, body=msg).execute()
return message
except errors.HttpError as error:
print ('An error occurred:' , error)
if __name__ == '__main__':
service_gmail = login('gmail2')
service_dnt = login('gmail1')
messages = listMessagesMatchingQuery(service_dnt,"me","in:spam is:unread after:" + str(int((datetime.now() - timedelta(hours=12)).timestamp())))
# this gets me some recent unread spam messages
m=getMessage(service_dnt,"me",messages[0]['id'])
# now I have a full message - I'm just investigating for now so the first message is enough
del m['threadId']
# if I don't do that, the error I get is that threadId is not allowed here, so I remove it
imported = importMessage(service_gmail,"me",m)
# this now gives me error 400 : 'raw' RFC822 payload message string or uploading message via /upload/* URL required
I'd like to find the way to make this work, so that the e-mail appears in gmail2 as if it had been received by gmail2 directly (though I would like to keep the To: address, as I use a catch-all on gmail1 and want to know which e-mail address the e-mail was directed to). But right now, I get only errors about having to use upload; I'm not sure if that's what I really should be doing, and if it is , I have no idea how.
Thanks a lot in advance for any help!
In the end I managed. I had missed the fact that there are 2 ways to "get" messages, a simple one and a "raw" one. With the "raw" way to access the message, I can use the import_ function easily:
message = service_dnt.users().messages().get(userId="me", id=messageId,format='raw').execute()
imported = importMessage(service_gmail,"me",{'raw': message['raw']})
whereby the importMessage function is unchanged vs. the original code I posted.
I'm trying to set up token authentication with the Django Rest Framework. I'm currently writing some tests to see if I can get a token returned for a user. Below is the code for the unit test (which is inside of a test case).
def test_create_valid_request(self):
u = User.objects.create(username='test1', password='thisis8chars')
Token.objects.create(user=u)
# these assertions all pass
self.assertEqual(User.objects.get(username='test1'), u)
self.assertEqual(u.username, 'test1')
self.assertEqual(u.password, 'thisis8chars')
data = {'username': 'test1', 'password': 'thisis8chars'}
url = "/api-token-auth/"
response = self.client.post(url, data, format="json")
print response.status_code
print response.content
This prints:
400
{"non_field_errors":["Unable to log in with provided credentials."]}
I understand that there must be something wrong with my credentials, but I can't see it. I create a user, tests its attributes, and make a post request to retrieve the token. I've manually tested this on the Django development server with httpie, and it works and returns the token. Any ideas what the problem could be? Is this a problem with my testing setup? If so, what?
I can post/describe more code if necessary.
Thanks
Okay so the error was very simple: I wanted User.objects.create_user rather than User.objects.create.
The password that I was trying to use with my code above was problematic because it wasn't hashed or salted, and because Django doesn't store or send plain-text passwords, me sending the plain-text password was resulting in a bad credentials error.
As you've already stated, you need to use User.objects.create_user.
To add to this, if you already have a User object instantiated and want to change their password you'll need to call the user.set_password(raw_password) method.
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',
}
}