Plain is headed towards 1.0! Subscribe for development updates →

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)