web2py How lo log out a user - python

I have just started looking at web2py, so many things in the approach are nwe, such as the use of defaults for views etc.
I spent many hours trying to figure out how to get people to log in and in the end put
#auth.requires_login()
at the start of a controller method. This magically brought up a registration form after which the new user was logged in.
I am not sure I fully understand what is going on behind the scenes, but one step at a time.
However, I would like to provide a logout button and have no idea how that might be achieved. Clearly I need to somehow call the default logout. So I need to add a url to the submit form/button and presumably create a controller to match the url, but what will the method to logout look like?
In the user method are the exposes statements, but no idea what they mean or how to use them. All those dots confuse me.

I spent many hours trying to figure out how to get people to log in
Did you read this section of the book? It explains the use of #auth.requires_login() and how the default user() function works to expose the login functionality (as well how to create a separate action specifically for login if desired). In the scaffolding application, the default.py controller includes the following user() function to expose all of the Auth actions:
def user():
return dict(form=auth())
In a URL like "/myapp/default/user/[action]", the requested Auth action (i.e., the last element of the URL) will be available in request.args[0]. When the above function calls auth(), the Auth __call__() method reads the action from request.args[0] and then calls the appropriate Auth method. If the URL is "/myapp/default/user/login", auth() will ultimately call the auth.login() method, which will return (and handle the processing of) the login form. You can also call auth.login() directly if you want to create your own custom login action.
Similarly, the URL "/myapp/default/user/logout" will ultimately call the auth.logout() method, which will log out the user. So, if you want to enable logout, just generate a link to the logout URL -- the best way is to use the URL() function:
A('Logout', _href=URL('default', 'user', args='logout'))
Note, in the scaffolding application, the layout.html view uses auth.navbar() to insert the Auth navbar in the upper right of the page -- when a user is logged in, that navbar automatically includes a "Logout" link like the one above.

Related

Pyramid: Overwrite Routing Rules Based on User Status

I am working on a small Pyramid web application (Python). One requirement is that the user resets the password after a fixed period of time, say, once a year. (I don't consider this requirement to help in terms of security, however, the customer's internal policy requires it.)
The password update should take place after a successful login. It is easy to add the logic to display the update password dialog after the login, but it is also possible that the user just navigates away using a bookmarked URL or the like.
What I would need to do is to overwrite the routing rules based on a property of the logged in user. However, the routing configuration seems to be just a set of static rules in Pyramid.
Is there a way to overwrite the routing rules based on the user's state, so that all requests to other views are forwarded/redirected to the password update dialog?
Assuming you have class based views and that you have set a session variable is_password_recent which is True if less than one year ago, you could put the following into each class's __init__():
class UserViews:
def __init__(self, request):
self.request = request
if not request.session["is_password_recent"]:
raise HTTPFound(
location=request.route_url(
"password_renew", _query={"return": request.path}
)
)
You could probably DRY it, too, by putting the logic into a method in a utils module and import it.
I found a solution using Pyramid events:
from pyramid.events import NewRequest, subscriber
#subscriber(NewRequest)
def intercept(event):
# use event.request to check if an interception/redirect is required
raise HTTPFound(location='route_to_form')
It is important to check the incoming request thoroughly. For example, paths including the route being used further in the process must be excluded, otherwise the application ends up in an infinite loop.

Programmatically Flask login during tests

How can I programmatically (without posting credentials to the login page like I'm currently doing) login an user during tests using Flask-login?
I tried the solution described here (which seems the only one for a programmatically login), but in my case it doesn't work, I get a 302 response because the unauthorized_handler is triggered like if the user is not logged in (my handler redirects to the login page if user is not authenticated).
I solved! And IMO I think is the most elegant and easy solution possible:
# we need this:
from flask_login import encode_cookie
# ...then in the test:
with self.client.session_transaction():
self.client.set_cookie(
self.app.config['SERVER_NAME'],
'remember_token',
encode_cookie(user_id)
)
# assertions here... User is now logged in! \0/
The actual code is just 2 lines (here I formatted just for readability :P)

How to limit one session from any browser for a username in flask?

I am using a gunicorn server in which I am trying to figure out a way to limit only one session per username i.e. if user A is logged in to the app from Chrome he should not be able to login through Firefox unless he logs out of chrome, or shouldn`t be able to open another TAB in chrome itself.
How can I generate a unique id for the browser and store it in a DB so that until the user logs out or session expires, the user can`t login through any other browser.
A possible method of limiting sessions to a single tab involves creating a random token on page load and embedding this token into the page. This most recently generated token gets stored in the user's session as well. This will be similar to how various frameworks add validation tokens to prevent CSFR attacks.
Brief example:
User loads page in tab 1 in Firefox. Token1 is generated, embedded and stored in session
User loads page in tab 2 in Firefox. Token2 is generated, embedded and stored in session. This overwrites previous value.
User loads page in tab 1 in Chrome. Token3 is generated, embedded and stored in session. this overwrites previous value.
At this point, the user has the page open in 3 tabs. The user's session, though, only has Token3 stored. This method prevents the user from being locked out (different IP addresses, different user agent strings, incogneto mode, etc) because each new session simply generates a new token. The newest load becomes the active window, immediately invalidating all previous sessions.
Next, any time the page interacts with the server (clicks a link, submits data, etc.), the token embedded in the page is sent as well. The server validates that the passed token matches the token in the session. If they match, the action succeeds. If they do not match, the server returns a failure message.
You can generate random numbers in multiple ways, but you probably want something secure. We'll use the example from another question:
import string
import random
...
N = 20 # Length of the string we want, adjust as appropriate
''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(N))
This uses random.SystemRandom, which is more secure than simply using random.choice
On page load, you need to check if the existing token is valid, generate the random token and store this in the user's session. Since we want this everywhere, let's make a decorator first, to reduce duplicate code later. The decorator checks if the session is valid and if not you get to select what to do (insert your own logic). It also sets a session token. This is needed (or you need logic to exclude your main page) otherwise you'll hit an infinite loop where the user attempts to load the main page, doesn't have a token, fails and the process repeats. I have the token regenerating on each page load via the else clause. If you do not implement the if section, this decorator is pointless as both paths perform the same action and simply reset the token on page load. The logic in the if is what will prevent the user from having multiple sessions.
from flask import session
from functools import wraps
def random_string(length):
return ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(length))
def validate_token(f):
#wraps(f)
def wrapper(existing_token, *args, **kwargs):
if session['token'] != existing_token:
# Logic on failure. Do you present a 404, do you bounce them back to your main page, do you do something else?
# It is IMPORTANT that you determine and implement this logic
# otherwise the decorator simply changes the token (and behaves the same way as the else block).
session['token'] = random_string(20)
else:
session['token'] = random_string(20)
return f(*args, **kwargs)
return wrapper
Now in our routes, we can apply this decorator to each, so that the user session gets updated on each page load:
from flask import render_template
#app.route('/path')
#validate_token
def path(token=None):
return render_template('path.html', token=session['token'])
In your template, you want to utilize this token value anywhere you need to prevent the session from continuing. For example, put it on links, in forms (though Flask has a method of CSRF protection already), etc. The server itself can check if the passed token is valid. The template could look as simple as this:
Click here to continue
I don't know how to implement it exactly, but it seems that you need websockets (See here)
This way, on every page load, you could have an Ajax request to the server, which would use the websocket functionality to query the previously opened browser+tab, if any. If it has an answer, it means that there is another browser+tab open. The server should then should return this information.
Your script which called the server through Ajax, depending on the return, should then decide to redirect to an error page, or to continue loading it.
I never used Websocket, but I'm pretty confident this would work (as is is well implemented in most browsers now).

How to show user a form to set username when using django with django-social-auth

I've been using django-social-auth (https://github.com/omab/django-social-auth), with some success - logging in from Twitter and Facebook have been set up without difficulty. However, when I log in from some OpenID providers, I am faced with an empty variable for the username, and the social-auth app allows this (despite me having set SOCIAL_AUTH_DEFAULT_USERNAME).
Whilst if SOCIAL_AUTH_DEFAULT_USERNAME worked properly that might be an interim solution, ideally I'd rather that it was either set automatically from the openID provider. Basically, I'm left with two possible solutions:
1) Make a custom method to extract some of the extra data sent from the openID provider to set the username from that.
2) Force the user to set a username when they first login.
Whilst (2) is less elegant, it ensures that a username has been inserted each time, and also obviates the need to have some postpocessing of the automatic information which may not be in a suitable format for the username.
My question amounts to, how can I go about implementing either of the above! Complete answers are not necessary, but pointers to a method would be much appreciated!
The alternative is to play with django-socialregistration and to see whether that makes life easier!
J
1 is probably the way to go. You can override the get_user_details method of the Backend class for the social providers you're having trouble with and pull the username from there.
It'd look something like this, for example for Facebook:
from social_auth.backends.facebook import FacebookBackend
class CustomFacebookBackend(FacebookBackend):
name = 'facebook'
def get_user_details(self, response):
"""Return user details from Facebook account"""
return {'username': response['name'],
'email': response.get('email', ''),
'first_name': response.get('first_name', ''),
'last_name': response.get('last_name', '')}
The "response" variable is the deserialized data returned from the request to the OAuth provider for the user details, so you can pull whatever you need from that, or inject your own values here. social-auth will take whatever you stick in "username" and use that as the username for the new account.
If you want more control, you can try overriding the backend's "username" method as well.
When overriding the backend, don't forget to add it to AUTHENTICATION_BACKENDS in settings.py. Also, I'm not sure how this works exactly, but I think you need to add something like this to the file with your custom backend in order to get social-auth to link your backend in properly:
BACKENDS = {
'facebook': CustomFacebookAuth,
}

action after sign in with google account

I'm new in google app engine, so my question may be kind of stupid or trivial, but anyway... For each user (for each google acc) I have an entity in my datastore. So when a new user logs in I want to add him to database.
I use:
- url: /.*
login: required
To make sure user is logged in with his google acc. The problem is when someone signs in from a subpage (blabla.appspot.com/something) then after log in he will be redirected to blabla.appspot.com/something and I have to verify in Request handler for "something" if the current user is already stored in database. How to do it without adding the same code to each request handler? Maybe it's possible to redirect to the main page after log in or something similar?
Here's a simple approach: If you create a subclass of webapp.RequestHandler that your handlers will then subclass, you can provide it with a convenience method for getting or creating a UserInfo object given users.get_current_user().user_id() (or .user_email(), if that's what you prefer to use as a key). Your handlers then call the convenience method in their get() and post() methods.
class MyRequestHandler(webapp.RequestHandler):
def setup_user_info(self):
# left as an exercise
self.user_info = user_info
and then
class MyHandler(MyRequestHandler):
def get(self):
self.setup_user_info()
...

Categories

Resources