mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	management: Add change_password command.
Zulip identifies users by realm+delivery_email which means that the Django changepassword command doesn't work well - since it looks only at the .email field. Thus we fork its code to our own change_password command.
This commit is contained in:
		
				
					committed by
					
						
						Tim Abbott
					
				
			
			
				
	
			
			
			
						parent
						
							bfe428f608
						
					
				
				
					commit
					c692263255
				
			
							
								
								
									
										69
									
								
								zerver/management/commands/change_password.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								zerver/management/commands/change_password.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,69 @@
 | 
				
			|||||||
 | 
					import getpass
 | 
				
			||||||
 | 
					from argparse import ArgumentParser
 | 
				
			||||||
 | 
					from typing import Any
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.contrib.auth.password_validation import validate_password
 | 
				
			||||||
 | 
					from django.core.exceptions import ValidationError
 | 
				
			||||||
 | 
					from django.core.management.base import CommandError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from zerver.lib.management import ZulipBaseCommand
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Command(ZulipBaseCommand):
 | 
				
			||||||
 | 
					    # This is our version of the original Django changepassword command adjusted
 | 
				
			||||||
 | 
					    # to be able to find UserProfiles by email+realm.
 | 
				
			||||||
 | 
					    # We change the arguments the command takes to fit our
 | 
				
			||||||
 | 
					    # model of username+realm and change accordingly the
 | 
				
			||||||
 | 
					    # logic inside the handle method which fetches the user
 | 
				
			||||||
 | 
					    # from the database. The rest of the logic remains unchanged.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    help = "Change a user's password."
 | 
				
			||||||
 | 
					    requires_migrations_checks = True
 | 
				
			||||||
 | 
					    requires_system_checks = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _get_pass(self, prompt: str = "Password: ") -> str:
 | 
				
			||||||
 | 
					        p = getpass.getpass(prompt=prompt)
 | 
				
			||||||
 | 
					        if not p:
 | 
				
			||||||
 | 
					            raise CommandError("aborted")
 | 
				
			||||||
 | 
					        return p
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def add_arguments(self, parser: ArgumentParser) -> None:
 | 
				
			||||||
 | 
					        parser.add_argument("email", metavar="<email>", help="email of user to change role")
 | 
				
			||||||
 | 
					        self.add_realm_args(parser, required=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def handle(self, *args: Any, **options: Any) -> str:
 | 
				
			||||||
 | 
					        email = options["email"]
 | 
				
			||||||
 | 
					        realm = self.get_realm(options)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        u = self.get_user(email, realm)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Code below is taken from the Django version of this command:
 | 
				
			||||||
 | 
					        self.stdout.write(f"Changing password for user '{u}'")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        MAX_TRIES = 3
 | 
				
			||||||
 | 
					        count = 0
 | 
				
			||||||
 | 
					        p1, p2 = "1", "2"  # To make them initially mismatch.
 | 
				
			||||||
 | 
					        password_validated = False
 | 
				
			||||||
 | 
					        while (p1 != p2 or not password_validated) and count < MAX_TRIES:
 | 
				
			||||||
 | 
					            p1 = self._get_pass()
 | 
				
			||||||
 | 
					            p2 = self._get_pass("Password (again): ")
 | 
				
			||||||
 | 
					            if p1 != p2:
 | 
				
			||||||
 | 
					                self.stdout.write("Passwords do not match. Please try again.")
 | 
				
			||||||
 | 
					                count += 1
 | 
				
			||||||
 | 
					                # Don't validate passwords that don't match.
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                validate_password(p2, u)
 | 
				
			||||||
 | 
					            except ValidationError as err:
 | 
				
			||||||
 | 
					                self.stderr.write("\n".join(err.messages))
 | 
				
			||||||
 | 
					                count += 1
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                password_validated = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if count == MAX_TRIES:
 | 
				
			||||||
 | 
					            raise CommandError(f"Aborting password change for user '{u}' after {count} attempts")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        u.set_password(p1)
 | 
				
			||||||
 | 
					        u.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return f"Password changed successfully for user '{u}'"
 | 
				
			||||||
		Reference in New Issue
	
	Block a user