Creating an authentication system using flask-simpleldap on Flask - flask

I'm trying to figure out how to use LDAP authentication to access my webpages and can't quite figure out how I'm supposed to be doing this.
I already get authentication from LDAP, I've tested that it's working, but now I'm trying to allow access only if a user is authenticated. I've followed multiple guides from the internet and have the following:
#app.route('/', methods=["GET", "POST"])
def index():
title = "Tools"
if request.method == "POST":
username = request.form["username"]
password = request.form["password"]
auth = ldap.bind_user(username, password)
if auth is None or password == "":
return render_template("login/form.html", error="Invalid credentials, try again.")
else:
g.user = request.form["username"]
return render_template("index.html", title=title, version=version)
if g.user:
return render_template("index.html", title=title, version=version)
else:
return render_template("login.html", title=title)
This is what I have so far and I'm currently getting
File "/home/dev/app.py", line 45, in index
if g.user:
File "/home/dev/.venv/ansible/lib/python3.8/site-packages/flask/ctx.py", line 51, in __getattr__
raise AttributeError(name) from None
AttributeError: user
Should I not be using g? I will need to keep passwords in the session in the future to connect to other API's using requests HTTPBasicAuth, and I thought g stores on server-side instead of client.
Any insight would be appreciated, thank you.

Related

how to access a firebase token and use as a decorator? [duplicate]

This question already has answers here:
How to pass a variable between Flask pages?
(2 answers)
Closed last year.
I want some of my flask routes to be authenticated before accessing, meaning any routes with the decorator #check_token will have to have a valid token prior to accessing the route.
I have a login_or_create route where users can either login or create an account onto Firebase.
From the login logic, a token is generated, and I want to pass that token to the home route. I feel my code is almost there, but I'm not sure how to persist the token into the next route.
def check_token(f):
#wraps(f)
def wrap(*args,**kwargs):
if not request.headers.get('authorization'):
return {'message': 'No token provided'},400
try:
user = auth.verify_id_token(request.headers['authorization'])
request.user = user
except:
return {'message':'Invalid token provided.'},400
return f(*args, **kwargs)
return wrap
#app.route("/", methods=['GET', 'POST'])
def login_or_create():
if request.method == 'POST':
#Handle login form
if request.form['action'] == 'login':
data = request.form
email = data.get('user_email')
password = data.get('user_password')
try:
signin_user = pb.auth().sign_in_with_email_and_password(email, password)
token = signin_user['idToken']
return redirect(url_for('home'))
except:
return {'message':'There was an error logging in'}, 400
#app.route("/home")
#check_token
def home():
return render_template('home_page.html)'
I ended up saving the token in a session variable. I think there are drawbacks to this, but this has been my solution thus far.

How does Django log in multiple users in a browser and how to avoid session coverage?

This is mine views.py file
class UserAPIView(TemplateView, ListCreateAPIView):
serializer_class = UserSerializer
queryset = UserProfile.objects.all()
template_name = 'users/login.html'
def post(self, request, *args, **kwargs):
if self.find_password_and_user(request):
return HttpResponseRedirect(reverse('user:home'))
else:
return HttpResponse("False")
def find_password_and_user(self, request):
print(request)
post_username = request.data.get('username')
post_password = request.data.get('password')
user = authenticate(username=post_username, password=post_password)
if user is not None:
# login(request, user)
# =====
backend = None
session_auth_hash = ''
if hasattr(user, 'get_session_auth_hash'):
session_auth_hash = user.get_session_auth_hash()
if SESSION_KEY in request.session:
if _get_user_session_key(request) != user.pk or (
session_auth_hash and
not constant_time_compare(request.session.get(HASH_SESSION_KEY, ''), session_auth_hash)):
# To avoid reusing another user's session, create a new, empty
# session if the existing session corresponds to a different
# authenticated user.
request.session.flush()
else:
request.session.cycle_key()
try:
backend = backend or user.backend
except AttributeError:
backends = _get_backends(return_tuples=True)
if len(backends) == 1:
_, backend = backends[0]
else:
raise ValueError(
'You have multiple authentication backends configured and '
'therefore must provide the `backend` argument or set the '
'`backend` attribute on the user.'
)
else:
if not isinstance(backend, str):
raise TypeError('backend must be a dotted import path string (got %r).' % backend)
request.session[SESSION_KEY] = user._meta.pk.value_to_string(user)
request.session[BACKEND_SESSION_KEY] = backend
request.session[HASH_SESSION_KEY] = session_auth_hash
if hasattr(request, 'user'):
request.user = user
rotate_token(request)
user_logged_in.send(sender=user.__class__, request=request, user=user)
# =====
user = UserProfile.objects.filter(username=post_username)
u_password = user.values('password')[0].get('password')
return check_password(post_password, u_password)
return False
How can I log in multiple users in one browser at the same time
By default, the session of the newly logged in user will override the session of the previous user. (in Django_ In the session table, the session of the previously logged in user is overwritten.)
If it is a different browser, it will not be covered. In short, the same browser can only log in to one user at the same time.
On the Internet, there is a way to change the session into a list, but there is no clue at all. I can't help it. Thank you very much.
The answer above got two steps. I should have said something wrong. But I haven't found a solution to this problem for nearly a day. This is my first time to use stack overflow, which is also a memorial. ha-ha. Thank you

Django fresh login required on accessing profile page

I am new to django and writing authentication system for my application. Right now I have built the basic login/logout functionality.
What I want is that whenever a logged in user wants to access their profile page, they should be asked to confirm their password.
I have searched this on django docs but I was not able to find any resource on how to achieve. I know flask has a fresh_login_required decorator which does this, just wondering if it is possible to do the same thing with django as well.
I don't think there is any function for this in django. But you can write on your own with help of django session. For example:
First, we need to write a decorator:
# decorator
def require_fresh_login(function):
#wraps(function)
def wrap(request, *args, **kwargs):
has_verified_profile = request.session.pop('has_login_verified',None)
if has_verified_profile:
return function(request, *args, **kwargs)
else:
return redirect(reverse('fresh_password_view_url'))
return wrap
Then there should be a view for fresh_password_view_url, where you need to put a value against key has_login_verified in request.session. For example:
def verify_fresh_password(request):
form = SomeForm(request.POST)
if form.is_valid():
password = form.cleaned_data.get('password')
if request.user.check_password(password):
request.session['has_login_verified'] = True
return redirect('profile_view')
# else send error response

user.is_authenticated returns False with custom backend

I've created a custom backend for my application in order to let people log in with ldap. Seems like everything is working, apart from one thing:
I am checking if "user.is_authenticated" in template to show "Log out" button for authenticated users, and it seems to return false all the time.
I am using standard django LoginView. Before I added my custom backend it all worked just fine, and I only rewrote the "authenticate()" function the way it says in django docs.
How could I fix it?
My backend is:
class MyBackEnd(object):
"""
This is the custom backend to authenticate the user in the DB.
if this authentication fais then django default authentication will get called
"""
def authenticate(self, request, username, password):
#here comes server address and search templates
try:
return User.objects.get(username=username)
except User.DoesNotExist:
try:
l = ldap.initialize(server)
l.protocol_version = 3
l.set_option(ldap.OPT_REFERRALS, 0)
l.simple_bind_s(username, password)
r = l.search(base, scope, filter, attrs)
type, user = l.result(r, 60)
if len(user) == 1:
user = User.objects.create_user(username=username, password=password)
user.save()
return user
except:
print("Failed to connect with ldap")
return None
def get_user(self, user_id):
try:
return User.objects.get(username=user_id)
except User.DoesNotExist:
return None
In function get_user:
return User.objects.get(username=user_id)
Is username stands for primary key in User model?
Try to replace following line by this:
return User.objects.get(pk=user_id)

Custom django authentication backend doesn't log user in first time, but works second time

So I'm using Rdio to login and create users, and wrote a backend to handle its oauth. The first time you try to sign in using Rdio, it creates a user and an attached Rdio user, but it doesn't create a session and return the session cookie.
The flow is like any oauth2 flow: you press a button on my app, it redirects w/ get params to Rdio, and Rdio calls a callback view on my app (along with a code in the GET params). In that callback view, I call authenticate:
class RdioCallbackView(View):
def get(self, request):
""" here, you need to create and auth a django user and create and tie the rdio user's stuff to it """
if request.user.is_authenticated() == False:
try:
rdio_code = request.GET['code']
except KeyError:
return redirect(reverse('login'))
# authenticate
user = auth.authenticate(rdio_code=rdio_code)
if user is not None and user.is_active:
auth.login(request, user)
else:
return render(request, 'home/login.html', {'rdio_url': create_rdio_auth_url(), 'message': "That code didn't seem to work"})
else:
# user exists!
user = request.user
return HttpResponseRedirect(reverse('the-next-view'))
The custom auth backend looks like this:
class RdioBackend(object):
def authenticate(self, rdio_code=None):
token_info = exchange_rdio_code(rdio_code)
try:
access_token = token_info['access_token']
refresh_token = token_info['refresh_token']
except KeyError:
return None
except TypeError:
# the code was probably already used.
return None
rdio_user_dict = get_rdio_user_for_access_token(access_token)
rdio_key = rdio_user_dict['key']
try:
rdio_user = RdioUser.objects.get(rdio_id=rdio_key)
rdio_user.access_token = access_token
rdio_user.refresh_token = refresh_token
rdio_user.save()
user = rdio_user.user
except RdioUser.DoesNotExist:
user = User.objects.create(username=rdio_key)
user.set_unusable_password()
rdio_user = RdioUser.objects.create(
rdio_id = rdio_key,
access_token = access_token,
refresh_token = token_info['refresh_token'],
user = user,
)
return user
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
And that's where things get weird. It doesn't seem to make a new Session object, and definitely doesn't return a session cookie. However, when I go back and do the Rdio login again for a second time, it returns a session cookie, makes the session on the backend, and login and auth work perfectly.
And I think my AUTHENTICATION_BACKENDS settings is right:
AUTHENTICATION_BACKENDS = (
'appname.backend.RdioBackend',
'django.contrib.auth.backends.ModelBackend',
)
Edit: More possibly relevant info:
The views that it's redirecting to have a LoginRequiredMixin:
class LoginRequiredMixin(object):
#classmethod
def as_view(cls, **initkwargs):
view = super(LoginRequiredMixin, cls).as_view(**initkwargs)
return login_required(view)
And in RdioCallbackView, when I change the final line from return HttpResponseRedirect(reverse('the-next-view')) to instead just serve the template directly with return render(request, 'path/to.html', param_dict), it does serve the cookie and make a sessionid, but then it deletes it from the DB and from the browser the moment I navigate away from that screen.
This might be the dumbest bug ever. It turns out that if you create a user without a password, you don't need to call user.set_unusable_password(). And if you do call user.set_unusable_password(), it somehow messes with any auth you do (even AFTER you call that).
So to fix this, I just got rid of the call to user.set_unusable_password() in my custom django auth backend.