plain.password
Password authentication for Plain.
1# Avoid shadowing the login() and logout() views below.
2from plain.auth.sessions import USER_HASH_SESSION_KEY, get_session_auth_hash
3from plain.auth.sessions import login as auth_login
4from plain.http import (
5 ResponseRedirect,
6)
7from plain.urls import NoReverseMatch, reverse
8from plain.utils.functional import Promise
9from plain.views import CreateView, FormView
10
11from .forms import (
12 # PasswordChangeForm,
13 # PasswordResetForm,
14 # SetPasswordForm,
15 PasswordLoginForm,
16 PasswordSignupForm,
17)
18
19
20def resolve_url(to, *args, **kwargs):
21 """
22 Return a URL appropriate for the arguments passed.
23
24 The arguments could be:
25
26 * A model: the model's `get_absolute_url()` function will be called.
27
28 * A view name, possibly with arguments: `urls.reverse()` will be used
29 to reverse-resolve the name.
30
31 * A URL, which will be returned as-is.
32 """
33 # If it's a model, use get_absolute_url()
34 if hasattr(to, "get_absolute_url"):
35 return to.get_absolute_url()
36
37 if isinstance(to, Promise):
38 # Expand the lazy instance, as it can cause issues when it is passed
39 # further to some Python functions like urlparse.
40 to = str(to)
41
42 # Handle relative URLs
43 if isinstance(to, str) and to.startswith(("./", "../")):
44 return to
45
46 # Next try a reverse URL resolution.
47 try:
48 return reverse(to, args=args, kwargs=kwargs)
49 except NoReverseMatch:
50 # If this is a callable, re-raise.
51 if callable(to):
52 raise
53 # If this doesn't "feel" like a URL, re-raise.
54 if "/" not in to and "." not in to:
55 raise
56
57 # Finally, fall back and assume it's a URL
58 return to
59
60
61def update_session_auth_hash(request, user):
62 """
63 Updating a user's password logs out all sessions for the user.
64
65 Take the current request and the updated user object from which the new
66 session hash will be derived and update the session hash appropriately to
67 prevent a password change from logging out the session from which the
68 password was changed.
69 """
70 request.session.cycle_key()
71 if request.user == user:
72 request.session[USER_HASH_SESSION_KEY] = get_session_auth_hash(user)
73
74
75# Class-based password reset views
76# - PasswordResetView sends the mail
77# - PasswordResetDoneView shows a success message for the above
78# - PasswordResetConfirmView checks the link the user clicked and
79# prompts for a new password
80# - PasswordResetCompleteView shows a success message for the above
81
82
83# class PasswordContextMixin:
84# extra_context = None
85
86# def get_template_context(self):
87# context = super().get_template_context()
88# context.update(
89# {"title": self.title, "subtitle": None, **(self.extra_context or {})}
90# )
91# return context
92
93
94# class PasswordResetView(PasswordContextMixin, FormView):
95# email_template_name = "auth/password_reset_email.html"
96# extra_email_context = None
97# form_class = PasswordResetForm
98# from_email = None
99# html_email_template_name = None
100# subject_template_name = "auth/password_reset_subject.txt"
101# success_url = reverse_lazy("password_reset_done")
102# template_name = "auth/password_reset_form.html"
103# title = "Password reset"
104# token_generator = default_token_generator
105
106# def form_valid(self, form):
107# opts = {
108# "use_https": self.request.is_secure(),
109# "token_generator": self.token_generator,
110# "from_email": self.from_email,
111# "email_template_name": self.email_template_name,
112# "subject_template_name": self.subject_template_name,
113# "html_email_template_name": self.html_email_template_name,
114# "extra_email_context": self.extra_email_context,
115# }
116# form.save(**opts)
117# return super().form_valid(form)
118
119
120# INTERNAL_RESET_SESSION_TOKEN = "_password_reset_token"
121
122
123# class PasswordResetDoneView(PasswordContextMixin, TemplateView):
124# template_name = "auth/password_reset_done.html"
125# title = "Password reset sent"
126
127
128# class PasswordResetConfirmView(PasswordContextMixin, FormView):
129# form_class = SetPasswordForm
130# post_reset_login = False
131# post_reset_login_backend = None
132# reset_url_token = "set-password"
133# success_url = reverse_lazy("password_reset_complete")
134# template_name = "auth/password_reset_confirm.html"
135# title = "Enter new password"
136# token_generator = default_token_generator
137
138# def get_response(self):
139# if "uidb64" not in self.url_kwargs or "token" not in self.url_kwargs:
140# raise ImproperlyConfigured(
141# "The URL path must contain 'uidb64' and 'token' parameters."
142# )
143
144# self.validlink = False
145# self.user = self.get_user(self.url_kwargs["uidb64"])
146
147# if self.user is not None:
148# token = self.url_kwargs["token"]
149# if token == self.reset_url_token:
150# session_token = self.request.session.get(INTERNAL_RESET_SESSION_TOKEN)
151# if self.token_generator.check_token(self.user, session_token):
152# # If the token is valid, display the password reset form.
153# self.validlink = True
154# response = super().get_response()
155# add_never_cache_headers(response)
156# return response
157# else:
158# if self.token_generator.check_token(self.user, token):
159# # Store the token in the session and redirect to the
160# # password reset form at a URL without the token. That
161# # avoids the possibility of leaking the token in the
162# # HTTP Referer header.
163# self.request.session[INTERNAL_RESET_SESSION_TOKEN] = token
164# redirect_url = self.request.path.replace(
165# token, self.reset_url_token
166# )
167# response = ResponseRedirect(redirect_url)
168# add_never_cache_headers(response)
169# return response
170
171# # Display the "Password reset unsuccessful" page.
172# response = self.render_to_response(self.get_template_context())
173# add_never_cache_headers(response)
174# return response
175
176# def get_user(self, uidb64):
177# UserModel = get_user_model()
178# try:
179# # urlsafe_base64_decode() decodes to bytestring
180# uid = urlsafe_base64_decode(uidb64).decode()
181# user = UserModel._default_manager.get(pk=uid)
182# except (
183# TypeError,
184# ValueError,
185# OverflowError,
186# UserModel.DoesNotExist,
187# ValidationError,
188# ):
189# user = None
190# return user
191
192# def get_form_kwargs(self):
193# kwargs = super().get_form_kwargs()
194# kwargs["user"] = self.user
195# return kwargs
196
197# def form_valid(self, form):
198# user = form.save()
199# del self.request.session[INTERNAL_RESET_SESSION_TOKEN]
200# if self.post_reset_login:
201# auth_login(self.request, user, self.post_reset_login_backend)
202# return super().form_valid(form)
203
204# def get_template_context(self):
205# context = super().get_template_context()
206# if self.validlink:
207# context["validlink"] = True
208# else:
209# context.update(
210# {
211# "form": None,
212# "title": "Password reset unsuccessful",
213# "validlink": False,
214# }
215# )
216# return context
217
218
219# class PasswordResetCompleteView(PasswordContextMixin, TemplateView):
220# template_name = "auth/password_reset_complete.html"
221# title = "Password reset complete"
222
223# def get_template_context(self):
224# context = super().get_template_context()
225# context["login_url"] = resolve_url(settings.AUTH_LOGIN_URL)
226# return context
227
228
229# class PasswordChangeView(PasswordContextMixin, AuthViewMixin, FormView):
230# form_class = PasswordChangeForm
231# success_url = reverse_lazy("password_change_done")
232# template_name = "auth/password_change_form.html"
233# title = "Password change"
234# login_required = True
235
236# def get_form_kwargs(self):
237# kwargs = super().get_form_kwargs()
238# kwargs["user"] = self.request.user
239# return kwargs
240
241# def form_valid(self, form):
242# form.save()
243# # Updating the password logs out all other sessions for the user
244# # except the current one.
245# update_session_auth_hash(self.request, form.user)
246# return super().form_valid(form)
247
248
249# class PasswordChangeDoneView(PasswordContextMixin, AuthViewMixin, TemplateView):
250# template_name = "auth/password_change_done.html"
251# title = "Password change successful"
252# login_required = True
253
254
255class PasswordLoginView(FormView):
256 form_class = PasswordLoginForm
257 success_url = "/"
258
259 def get(self):
260 # Redirect if the user is already logged in
261 if self.request.user:
262 return ResponseRedirect(self.success_url)
263
264 return super().get()
265
266 def form_valid(self, form):
267 # Log the user in and redirect
268 auth_login(self.request, form.get_user())
269
270 return super().form_valid(form)
271
272
273class PasswordSignupView(CreateView):
274 form_class = PasswordSignupForm
275 success_url = "/"
276
277 def form_valid(self, form):
278 # # Log the user in and redirect
279 # auth_login(self.request, form.save())
280
281 return super().form_valid(form)