From 84cd0527c20c2d41c2ed2f09ee1ce5841ecb008d Mon Sep 17 00:00:00 2001 From: Michael Webster Date: Thu, 4 Dec 2025 13:47:36 -0500 Subject: [PATCH 1/3] cs_user.py: Use AccountsService to change the password. Ref: https://bugzilla.redhat.com/show_bug.cgi?id=2418600 --- .../cinnamon-settings/modules/cs_user.py | 154 +++++------------- 1 file changed, 37 insertions(+), 117 deletions(-) diff --git a/files/usr/share/cinnamon/cinnamon-settings/modules/cs_user.py b/files/usr/share/cinnamon/cinnamon-settings/modules/cs_user.py index 96b20768bd..6ba6e44d08 100755 --- a/files/usr/share/cinnamon/cinnamon-settings/modules/cs_user.py +++ b/files/usr/share/cinnamon/cinnamon-settings/modules/cs_user.py @@ -1,14 +1,5 @@ #!/usr/bin/python3 -try: - import pam - print("Using pam module (python3-pampy)") -except: - import PAM - print("Using PAM module (python3-pam)") - pam = None -import pexpect -import time from random import randint import shutil import os @@ -17,16 +8,12 @@ from PIL import Image import gi gi.require_version('AccountsService', '1.0') -from gi.repository import AccountsService, GLib, GdkPixbuf, XApp +from gi.repository import AccountsService, GLib, GdkPixbuf, Gtk from SettingsWidgets import SidePage from ChooserButtonWidgets import PictureChooserButton from xapp.GSettingsWidgets import * -class PasswordError(Exception): - """Exception raised when an incorrect password is supplied.""" - pass - class Module: name = "user" @@ -236,43 +223,35 @@ def _on_realname_changed(self, widget, text): self.accountService.set_real_name(text) def _on_password_button_clicked(self, widget): - dialog = PasswordDialog() - response = dialog.run() + dialog = PasswordDialog(self.accountService) + dialog.run() class PasswordDialog(Gtk.Dialog): - def __init__ (self): + def __init__(self, accountService): super(PasswordDialog, self).__init__() - self.correct_current_password = False # Flag to remember if the current password is correct or not + self.accountService = accountService self.set_modal(True) self.set_skip_taskbar_hint(True) self.set_skip_pager_hint(True) self.set_title(_("Change Password")) + self.set_default_size(400, -1) - table = Gtk.Table(6, 3) + table = Gtk.Table(5, 3) table.set_border_width(6) table.set_row_spacings(8) - table.set_col_spacings(15) + table.set_col_spacings(8) - label = Gtk.Label.new(_("Current password")) + label = Gtk.Label(label=_("New password"), halign=Gtk.Align.END) label.set_alignment(1, 0.5) - table.attach(label, 0, 1, 0, 1) + table.attach(label, 0, 1, 0, 1, xoptions=Gtk.AttachOptions.FILL) - label = Gtk.Label.new(_("New password")) + label = Gtk.Label(label=_("Confirm password"), halign=Gtk.Align.END) label.set_alignment(1, 0.5) - table.attach(label, 0, 1, 1, 2) - - label = Gtk.Label.new(_("Confirm password")) - label.set_alignment(1, 0.5) - table.attach(label, 0, 1, 3, 4) - - self.current_password = Gtk.Entry() - self.current_password.set_visibility(False) - self.current_password.connect("focus-out-event", self._on_current_password_changed) - table.attach(self.current_password, 1, 3, 0, 1) + table.attach(label, 0, 1, 2, 3, xoptions=Gtk.AttachOptions.FILL) self.new_password = Gtk.Entry() self.new_password.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, "view-refresh") @@ -280,26 +259,26 @@ def __init__ (self): self.new_password.set_tooltip_text(_("Generate a password")) self.new_password.connect("icon-release", self._on_new_password_icon_released) self.new_password.connect("changed", self._on_passwords_changed) - table.attach(self.new_password, 1, 3, 1, 2) + table.attach(self.new_password, 1, 3, 0, 1) - self.strengh_indicator = Gtk.ProgressBar() + self.strengh_indicator = Gtk.ProgressBar(valign=Gtk.Align.CENTER) self.strengh_indicator.set_tooltip_text(_("Your new password needs to be at least 8 characters long")) self.strengh_indicator.set_fraction(0.0) - table.attach(self.strengh_indicator, 1, 2, 2, 3, xoptions=Gtk.AttachOptions.EXPAND|Gtk.AttachOptions.FILL) + table.attach(self.strengh_indicator, 1, 2, 1, 2) self.strengh_indicator.set_size_request(-1, 1) self.strengh_label = Gtk.Label() self.strengh_label.set_tooltip_text(_("Your new password needs to be at least 8 characters long")) self.strengh_label.set_alignment(1, 0.5) - table.attach(self.strengh_label, 2, 3, 2, 3) + table.attach(self.strengh_label, 2, 3, 1, 2) self.confirm_password = Gtk.Entry() self.confirm_password.connect("changed", self._on_passwords_changed) - table.attach(self.confirm_password, 1, 3, 3, 4) + table.attach(self.confirm_password, 1, 3, 2, 3) self.show_password = Gtk.CheckButton(_("Show password")) self.show_password.connect('toggled', self._on_show_password_toggled) - table.attach(self.show_password, 1, 3, 4, 5) + table.attach(self.show_password, 1, 3, 3, 4) self.set_border_width(6) @@ -309,12 +288,12 @@ def __init__ (self): self.infobar = Gtk.InfoBar() self.infobar.set_message_type(Gtk.MessageType.ERROR) - label = Gtk.Label.new(_("An error occurred. Your password was not changed.")) + self.infobar_label = Gtk.Label.new(_("An error occurred. Your password was not changed.")) content = self.infobar.get_content_area() - content.add(label) - table.attach(self.infobar, 0, 3, 5, 6) + content.add(self.infobar_label) + table.attach(self.infobar, 0, 3, 4, 5) - self.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, _("Change"), Gtk.ResponseType.OK, ) + self.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, _("Change"), Gtk.ResponseType.OK) self.set_passwords_visibility() self.set_response_sensitive(Gtk.ResponseType.OK, False) @@ -329,29 +308,16 @@ def _on_response(self, dialog, response_id): self.destroy() def change_password(self): - oldpass = self.current_password.get_text() + self.hide() + GLib.timeout_add(500, self._do_change_password) + + def _do_change_password(self): newpass = self.new_password.get_text() - passwd = pexpect.spawn("/usr/bin/passwd") - # passwd only asks for the old password when there already is one set. - if oldpass == "": - time.sleep(0.5) - passwd.sendline(newpass) - time.sleep(0.5) - passwd.sendline(newpass) - else: - time.sleep(0.5) - passwd.sendline(oldpass) - time.sleep(0.5) - passwd.sendline(newpass) - time.sleep(0.5) - passwd.sendline(newpass) - time.sleep(0.5) - passwd.close() - - if passwd.exitstatus is None or passwd.exitstatus > 0: - self.infobar.show_all() - else: - self.destroy() + + # AccountsService will automatically trigger polkit authentication + self.accountService.set_password(newpass, "") + self.destroy() + return GLib.SOURCE_REMOVE def set_passwords_visibility(self): visible = self.show_password.get_active() @@ -374,43 +340,6 @@ def _on_new_password_icon_released(self, widget, icon_pos, event): def _on_show_password_toggled(self, widget): self.set_passwords_visibility() - def auth_pam(self): - if not pam.pam().authenticate(GLib.get_user_name(), self.current_password.get_text(), 'passwd'): - raise PasswordError("Invalid password") - - def auth_PyPAM(self): - auth = PAM.pam() - auth.start('passwd') - auth.set_item(PAM.PAM_USER, GLib.get_user_name()) - auth.set_item(PAM.PAM_CONV, self.pam_conv) - try: - auth.authenticate() - auth.acct_mgmt() - return True - except PAM.error as resp: - raise PasswordError("Invalid password") - - def _on_current_password_changed(self, widget, event): - self.infobar.hide() - try: - self.auth_pam() if pam else self.auth_PyPAM() - except PasswordError: - self.current_password.set_icon_from_stock(Gtk.EntryIconPosition.SECONDARY, Gtk.STOCK_DIALOG_WARNING) - self.current_password.set_icon_tooltip_text(Gtk.EntryIconPosition.SECONDARY, _("Wrong password")) - self.current_password.set_tooltip_text(_("Wrong password")) - self.correct_current_password = False - except: - self.current_password.set_icon_from_stock(Gtk.EntryIconPosition.SECONDARY, Gtk.STOCK_DIALOG_WARNING) - self.current_password.set_icon_tooltip_text(Gtk.EntryIconPosition.SECONDARY, _("Internal Error")) - self.current_password.set_tooltip_text(_("Internal Error")) - self.correct_current_password = False - raise - else: - self.current_password.set_icon_from_stock(Gtk.EntryIconPosition.SECONDARY, None) - self.current_password.set_tooltip_text("") - self.correct_current_password = True - self.check_passwords() - # Based on setPasswordStrength() in Mozilla Seamonkey, which is tri-licensed under MPL 1.1, GPL 2.0, and LGPL 2.1. # Forked from Ubiquity validation.py def password_strength(self, password): @@ -472,18 +401,9 @@ def _on_passwords_changed(self, widget): self.check_passwords() def check_passwords(self): - if self.correct_current_password: - new_password = self.new_password.get_text() - confirm_password = self.confirm_password.get_text() - if len(new_password) >= 8 and new_password == confirm_password: - self.set_response_sensitive(Gtk.ResponseType.OK, True) - else: - self.set_response_sensitive(Gtk.ResponseType.OK, False) - - def pam_conv(self, auth, query_list, userData): - resp = [] - for i in range(len(query_list)): - query, type = query_list[i] - val = self.current_password.get_text() - resp.append((val, 0)) - return resp + new_password = self.new_password.get_text() + confirm_password = self.confirm_password.get_text() + if len(new_password) >= 8 and new_password == confirm_password: + self.set_response_sensitive(Gtk.ResponseType.OK, True) + else: + self.set_response_sensitive(Gtk.ResponseType.OK, False) From 0a13ec721b1760ab5172c4c219b79083c8bce4d9 Mon Sep 17 00:00:00 2001 From: Michael Webster Date: Thu, 4 Dec 2025 15:57:36 -0500 Subject: [PATCH 2/3] Use the same password dialog for both user details and cinnamon- settings-users. This moves the password dialog to its own file that can be utilized in both scenarios, now that they both rely on the system for handling authentication to perform the password change. --- .../cinnamon-settings-users.py | 176 +-------------- .../bin/ChangePasswordDialog.py | 202 ++++++++++++++++++ .../cinnamon-settings/modules/cs_user.py | 185 +--------------- 3 files changed, 209 insertions(+), 354 deletions(-) create mode 100644 files/usr/share/cinnamon/cinnamon-settings/bin/ChangePasswordDialog.py diff --git a/files/usr/share/cinnamon/cinnamon-settings-users/cinnamon-settings-users.py b/files/usr/share/cinnamon/cinnamon-settings-users/cinnamon-settings-users.py index f19fcfc94e..e84228b176 100755 --- a/files/usr/share/cinnamon/cinnamon-settings-users/cinnamon-settings-users.py +++ b/files/usr/share/cinnamon/cinnamon-settings-users/cinnamon-settings-users.py @@ -7,6 +7,7 @@ import shutil import re import subprocess +import sys from random import randint from setproctitle import setproctitle @@ -17,6 +18,9 @@ gi.require_version("AccountsService", "1.0") from gi.repository import Gtk, GObject, Gio, GdkPixbuf, AccountsService, GLib +sys.path.insert(0, '/usr/share/cinnamon/cinnamon-settings/bin') +from ChangePasswordDialog import ChangePasswordDialog + gettext.install("cinnamon", "/usr/share/locale") class PrivHelper(object): @@ -180,176 +184,6 @@ def get_editable(self): def get_text(self): return self.entry.get_text() -class PasswordDialog(Gtk.Dialog): - - def __init__ (self, user, password_mask, group_mask, parent = None): - super(PasswordDialog, self).__init__(None, parent) - - self.user = user - self.password_mask = password_mask - self.group_mask = group_mask - - self.set_modal(True) - self.set_skip_taskbar_hint(True) - self.set_skip_pager_hint(True) - self.set_title(_("Change Password")) - - table = DimmedTable() - table.add_labels([_("New password"), None, _("Confirm password")]) - - self.new_password = Gtk.Entry() - self.new_password.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, "xsi-view-refresh-symbolic") - self.new_password.set_icon_tooltip_text(Gtk.EntryIconPosition.SECONDARY, _("Generate a password")) - self.new_password.connect("icon-release", self._on_new_password_icon_released) - self.new_password.connect("changed", self._on_passwords_changed) - table.attach(self.new_password, 1, 3, 0, 1) - - self.strengh_indicator = Gtk.ProgressBar() - self.strengh_indicator.set_tooltip_text(_("Your new password needs to be at least 8 characters long")) - self.strengh_indicator.set_fraction(0.0) - table.attach(self.strengh_indicator, 1, 2, 1, 2, xoptions=Gtk.AttachOptions.EXPAND|Gtk.AttachOptions.FILL) - self.strengh_indicator.set_size_request(-1, 1) - - self.strengh_label = Gtk.Label() - self.strengh_label.set_tooltip_text(_("Your new password needs to be at least 8 characters long")) - self.strengh_label.set_alignment(1, 0.5) - table.attach(self.strengh_label, 2, 3, 1, 2) - - self.confirm_password = Gtk.Entry() - self.confirm_password.connect("changed", self._on_passwords_changed) - table.attach(self.confirm_password, 1, 3, 2, 3) - - self.show_password = Gtk.CheckButton(_("Show password")) - self.show_password.connect('toggled', self._on_show_password_toggled) - table.attach(self.show_password, 1, 3, 3, 4) - - self.set_border_width(6) - - box = self.get_content_area() - box.add(table) - self.show_all() - - self.infobar = Gtk.InfoBar() - self.infobar.set_message_type(Gtk.MessageType.ERROR) - label = Gtk.Label(_("An error occurred. Your password was not changed.")) - content = self.infobar.get_content_area() - content.add(label) - table.attach(self.infobar, 0, 3, 4, 5) - - self.add_buttons(_("Cancel"), Gtk.ResponseType.CANCEL, _("Change"), Gtk.ResponseType.OK, ) - - self.set_passwords_visibility() - self.set_response_sensitive(Gtk.ResponseType.OK, False) - self.infobar.hide() - - self.connect("response", self._on_response) - - def _on_response(self, dialog, response_id): - if response_id == Gtk.ResponseType.OK: - self.change_password() - else: - self.destroy() - - def change_password(self): - newpass = self.new_password.get_text() - self.user.set_password(newpass, "") - mask = self.group_mask.get_text() - if "nopasswdlogin" in mask: - subprocess.call(["gpasswd", "-d", self.user.get_user_name(), "nopasswdlogin"]) - mask = mask.split(", ") - mask.remove("nopasswdlogin") - mask = ", ".join(mask) - self.group_mask.set_text(mask) - self.password_mask.set_text('\u2022\u2022\u2022\u2022\u2022\u2022') - self.destroy() - - def set_passwords_visibility(self): - visible = self.show_password.get_active() - self.new_password.set_visibility(visible) - self.confirm_password.set_visibility(visible) - - def _on_new_password_icon_released(self, widget, icon_pos, event): - self.infobar.hide() - self.show_password.set_active(True) - characters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-" - newpass = "" - for i in range (8): - index = randint(0, len(characters) -1) - newpass = newpass + characters[index] - - self.new_password.set_text(newpass) - self.confirm_password.set_text(newpass) - self.check_passwords() - - def _on_show_password_toggled(self, widget): - self.set_passwords_visibility() - - # Based on setPasswordStrength() in Mozilla Seamonkey, which is tri-licensed under MPL 1.1, GPL 2.0, and LGPL 2.1. - # Forked from Ubiquity validation.py - def password_strength(self, password): - upper = lower = digit = symbol = 0 - for char in password: - if char.isdigit(): - digit += 1 - elif char.islower(): - lower += 1 - elif char.isupper(): - upper += 1 - else: - symbol += 1 - length = len(password) - - length = min(length,4) - digit = min(digit,3) - upper = min(upper,3) - symbol = min(symbol,3) - strength = ( - ((length * 0.1) - 0.2) + - (digit * 0.1) + - (symbol * 0.15) + - (upper * 0.1)) - if strength > 1: - strength = 1 - if strength < 0: - strength = 0 - return strength - - def _on_passwords_changed(self, widget): - self.infobar.hide() - new_password = self.new_password.get_text() - confirm_password = self.confirm_password.get_text() - strength = self.password_strength(new_password) - if new_password != confirm_password: - self.confirm_password.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, "xsi-dialog-warning-symbolic") - self.confirm_password.set_icon_tooltip_text(Gtk.EntryIconPosition.SECONDARY, _("Passwords do not match")) - else: - self.confirm_password.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, None) - if len(new_password) < 8: - self.strengh_label.set_text(_("Too short")) - self.strengh_indicator.set_fraction(0.0) - elif strength < 0.5: - self.strengh_label.set_text(_("Weak")) - self.strengh_indicator.set_fraction(0.2) - elif strength < 0.75: - self.strengh_label.set_text(_("Fair")) - self.strengh_indicator.set_fraction(0.4) - elif strength < 0.9: - self.strengh_label.set_text(_("Good")) - self.strengh_indicator.set_fraction(0.6) - else: - self.strengh_label.set_text(_("Strong")) - self.strengh_indicator.set_fraction(1.0) - - self.check_passwords() - - def check_passwords(self): - new_password = self.new_password.get_text() - confirm_password = self.confirm_password.get_text() - if len(new_password) >= 8 and new_password == confirm_password: - self.set_response_sensitive(Gtk.ResponseType.OK, True) - else: - self.set_response_sensitive(Gtk.ResponseType.OK, False) - class NewUserDialog(Gtk.Dialog): def __init__ (self, parent = None): @@ -609,7 +443,7 @@ def _on_password_button_clicked(self, widget): model, treeiter = self.users_treeview.get_selection().get_selected() if treeiter is not None: user = model[treeiter][INDEX_USER_OBJECT] - dialog = PasswordDialog(user, self.password_mask, self.groups_label, self.window) + dialog = ChangePasswordDialog(user, self.password_mask, self.groups_label, self.window) response = dialog.run() def _on_groups_button_clicked(self, widget): diff --git a/files/usr/share/cinnamon/cinnamon-settings/bin/ChangePasswordDialog.py b/files/usr/share/cinnamon/cinnamon-settings/bin/ChangePasswordDialog.py new file mode 100644 index 0000000000..f6438878a7 --- /dev/null +++ b/files/usr/share/cinnamon/cinnamon-settings/bin/ChangePasswordDialog.py @@ -0,0 +1,202 @@ +#!/usr/bin/python3 + +from random import randint +import subprocess + +import gi +gi.require_version('AccountsService', '1.0') +from gi.repository import AccountsService, GLib, Gtk + +class ChangePasswordDialog(Gtk.Dialog): + + def __init__ (self, accountsUser, password_mask_label=None, group_mask_label=None, parent = None): + super(ChangePasswordDialog, self).__init__() + + self.accountsUser = accountsUser + self.password_mask = password_mask_label + self.group_mask = group_mask_label + + self.set_modal(True) + self.set_skip_taskbar_hint(True) + self.set_skip_pager_hint(True) + self.set_title(_("Change Password")) + self.set_default_size(400, -1) + + table = Gtk.Table(5, 3) + table.set_border_width(6) + table.set_row_spacings(8) + table.set_col_spacings(8) + + label = Gtk.Label(label=_("New password"), halign=Gtk.Align.END) + label.set_alignment(1, 0.5) + table.attach(label, 0, 1, 0, 1, xoptions=Gtk.AttachOptions.FILL) + + label = Gtk.Label(label=_("Confirm password"), halign=Gtk.Align.END) + label.set_alignment(1, 0.5) + table.attach(label, 0, 1, 2, 3, xoptions=Gtk.AttachOptions.FILL) + + self.new_password = Gtk.Entry() + self.new_password.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, "view-refresh-symbolic") + self.new_password.set_icon_tooltip_text(Gtk.EntryIconPosition.SECONDARY, _("Generate a password")) + self.new_password.set_tooltip_text(_("Generate a password")) + self.new_password.connect("icon-release", self._on_new_password_icon_released) + self.new_password.connect("changed", self._on_passwords_changed) + table.attach(self.new_password, 1, 3, 0, 1) + + self.strengh_indicator = Gtk.ProgressBar(valign=Gtk.Align.CENTER) + self.strengh_indicator.set_tooltip_text(_("Your new password needs to be at least 8 characters long")) + self.strengh_indicator.set_fraction(0.0) + table.attach(self.strengh_indicator, 1, 2, 1, 2) + self.strengh_indicator.set_size_request(-1, 1) + + self.strengh_label = Gtk.Label() + self.strengh_label.set_tooltip_text(_("Your new password needs to be at least 8 characters long")) + self.strengh_label.set_alignment(1, 0.5) + table.attach(self.strengh_label, 2, 3, 1, 2) + + self.confirm_password = Gtk.Entry() + self.confirm_password.connect("changed", self._on_passwords_changed) + table.attach(self.confirm_password, 1, 3, 2, 3) + + self.show_password = Gtk.CheckButton(_("Show password")) + self.show_password.connect('toggled', self._on_show_password_toggled) + table.attach(self.show_password, 1, 3, 3, 4) + + self.set_border_width(6) + + box = self.get_content_area() + box.add(table) + self.show_all() + + self.infobar = Gtk.InfoBar() + self.infobar.set_message_type(Gtk.MessageType.ERROR) + self.infobar_label = Gtk.Label.new(_("An error occurred. Your password was not changed.")) + content = self.infobar.get_content_area() + content.add(self.infobar_label) + table.attach(self.infobar, 0, 3, 4, 5) + + self.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, _("Change"), Gtk.ResponseType.OK) + + self.set_passwords_visibility() + self.set_response_sensitive(Gtk.ResponseType.OK, False) + self.infobar.hide() + + self.connect("response", self._on_response) + + def _on_response(self, dialog, response_id): + if response_id == Gtk.ResponseType.OK: + self.change_password() + else: + self.destroy() + + def change_password(self): + self.hide() + GLib.timeout_add(500, self._do_change_password) + + def _do_change_password(self): + newpass = self.new_password.get_text() + + # AccountsService will automatically trigger polkit authentication + self.accountsUser.set_password(newpass, "") + + if self.group_mask is not None: + mask = self.group_mask.get_text() + if "nopasswdlogin" in mask: + subprocess.call(["gpasswd", "-d", self.accountsUser.get_user_name(), "nopasswdlogin"]) + mask = mask.split(", ") + mask.remove("nopasswdlogin") + mask = ", ".join(mask) + self.group_mask.set_text(mask) + self.password_mask.set_text('\u2022\u2022\u2022\u2022\u2022\u2022') + + self.destroy() + return GLib.SOURCE_REMOVE + + def set_passwords_visibility(self): + visible = self.show_password.get_active() + self.new_password.set_visibility(visible) + self.confirm_password.set_visibility(visible) + + def _on_new_password_icon_released(self, widget, icon_pos, event): + self.infobar.hide() + self.show_password.set_active(True) + characters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-" + newpass = "" + for i in range (8): + index = randint(0, len(characters) -1) + newpass = newpass + characters[index] + + self.new_password.set_text(newpass) + self.confirm_password.set_text(newpass) + self.check_passwords() + + def _on_show_password_toggled(self, widget): + self.set_passwords_visibility() + + # Based on setPasswordStrength() in Mozilla Seamonkey, which is tri-licensed under MPL 1.1, GPL 2.0, and LGPL 2.1. + # Forked from Ubiquity validation.py + def password_strength(self, password): + upper = lower = digit = symbol = 0 + for char in password: + if char.isdigit(): + digit += 1 + elif char.islower(): + lower += 1 + elif char.isupper(): + upper += 1 + else: + symbol += 1 + length = len(password) + + length = min(length,4) + digit = min(digit,3) + upper = min(upper,3) + symbol = min(symbol,3) + strength = ( + ((length * 0.1) - 0.2) + + (digit * 0.1) + + (symbol * 0.15) + + (upper * 0.1)) + if strength > 1: + strength = 1 + if strength < 0: + strength = 0 + return strength + + def _on_passwords_changed(self, widget): + self.infobar.hide() + new_password = self.new_password.get_text() + confirm_password = self.confirm_password.get_text() + strength = self.password_strength(new_password) + if new_password != confirm_password: + self.confirm_password.set_icon_from_stock(Gtk.EntryIconPosition.SECONDARY, Gtk.STOCK_DIALOG_WARNING) + self.confirm_password.set_icon_tooltip_text(Gtk.EntryIconPosition.SECONDARY, _("Passwords do not match")) + self.confirm_password.set_tooltip_text(_("Passwords do not match")) + else: + self.confirm_password.set_icon_from_stock(Gtk.EntryIconPosition.SECONDARY, None) + self.confirm_password.set_tooltip_text("") + if len(new_password) < 8: + self.strengh_label.set_text(_("Too short")) + self.strengh_indicator.set_fraction(0.0) + elif strength < 0.6: + self.strengh_label.set_text(_("Weak")) + self.strengh_indicator.set_fraction(0.2) + elif strength < 0.75: + self.strengh_label.set_text(_("Fair")) + self.strengh_indicator.set_fraction(0.4) + elif strength < 0.9: + self.strengh_label.set_text(_("Good")) + self.strengh_indicator.set_fraction(0.6) + else: + self.strengh_label.set_text(_("Strong")) + self.strengh_indicator.set_fraction(1.0) + + self.check_passwords() + + def check_passwords(self): + new_password = self.new_password.get_text() + confirm_password = self.confirm_password.get_text() + if len(new_password) >= 8 and new_password == confirm_password: + self.set_response_sensitive(Gtk.ResponseType.OK, True) + else: + self.set_response_sensitive(Gtk.ResponseType.OK, False) diff --git a/files/usr/share/cinnamon/cinnamon-settings/modules/cs_user.py b/files/usr/share/cinnamon/cinnamon-settings/modules/cs_user.py index 6ba6e44d08..4bef4cd9c0 100755 --- a/files/usr/share/cinnamon/cinnamon-settings/modules/cs_user.py +++ b/files/usr/share/cinnamon/cinnamon-settings/modules/cs_user.py @@ -13,7 +13,7 @@ from SettingsWidgets import SidePage from ChooserButtonWidgets import PictureChooserButton from xapp.GSettingsWidgets import * - +from ChangePasswordDialog import ChangePasswordDialog class Module: name = "user" @@ -223,187 +223,6 @@ def _on_realname_changed(self, widget, text): self.accountService.set_real_name(text) def _on_password_button_clicked(self, widget): - dialog = PasswordDialog(self.accountService) + dialog = ChangePasswordDialog(self.accountService) dialog.run() - -class PasswordDialog(Gtk.Dialog): - - def __init__(self, accountService): - super(PasswordDialog, self).__init__() - - self.accountService = accountService - - self.set_modal(True) - self.set_skip_taskbar_hint(True) - self.set_skip_pager_hint(True) - self.set_title(_("Change Password")) - self.set_default_size(400, -1) - - table = Gtk.Table(5, 3) - table.set_border_width(6) - table.set_row_spacings(8) - table.set_col_spacings(8) - - label = Gtk.Label(label=_("New password"), halign=Gtk.Align.END) - label.set_alignment(1, 0.5) - table.attach(label, 0, 1, 0, 1, xoptions=Gtk.AttachOptions.FILL) - - label = Gtk.Label(label=_("Confirm password"), halign=Gtk.Align.END) - label.set_alignment(1, 0.5) - table.attach(label, 0, 1, 2, 3, xoptions=Gtk.AttachOptions.FILL) - - self.new_password = Gtk.Entry() - self.new_password.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, "view-refresh") - self.new_password.set_icon_tooltip_text(Gtk.EntryIconPosition.SECONDARY, _("Generate a password")) - self.new_password.set_tooltip_text(_("Generate a password")) - self.new_password.connect("icon-release", self._on_new_password_icon_released) - self.new_password.connect("changed", self._on_passwords_changed) - table.attach(self.new_password, 1, 3, 0, 1) - - self.strengh_indicator = Gtk.ProgressBar(valign=Gtk.Align.CENTER) - self.strengh_indicator.set_tooltip_text(_("Your new password needs to be at least 8 characters long")) - self.strengh_indicator.set_fraction(0.0) - table.attach(self.strengh_indicator, 1, 2, 1, 2) - self.strengh_indicator.set_size_request(-1, 1) - - self.strengh_label = Gtk.Label() - self.strengh_label.set_tooltip_text(_("Your new password needs to be at least 8 characters long")) - self.strengh_label.set_alignment(1, 0.5) - table.attach(self.strengh_label, 2, 3, 1, 2) - - self.confirm_password = Gtk.Entry() - self.confirm_password.connect("changed", self._on_passwords_changed) - table.attach(self.confirm_password, 1, 3, 2, 3) - - self.show_password = Gtk.CheckButton(_("Show password")) - self.show_password.connect('toggled', self._on_show_password_toggled) - table.attach(self.show_password, 1, 3, 3, 4) - - self.set_border_width(6) - - box = self.get_content_area() - box.add(table) - self.show_all() - - self.infobar = Gtk.InfoBar() - self.infobar.set_message_type(Gtk.MessageType.ERROR) - self.infobar_label = Gtk.Label.new(_("An error occurred. Your password was not changed.")) - content = self.infobar.get_content_area() - content.add(self.infobar_label) - table.attach(self.infobar, 0, 3, 4, 5) - - self.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, _("Change"), Gtk.ResponseType.OK) - - self.set_passwords_visibility() - self.set_response_sensitive(Gtk.ResponseType.OK, False) - self.infobar.hide() - - self.connect("response", self._on_response) - - def _on_response(self, dialog, response_id): - if response_id == Gtk.ResponseType.OK: - self.change_password() - else: - self.destroy() - - def change_password(self): - self.hide() - GLib.timeout_add(500, self._do_change_password) - - def _do_change_password(self): - newpass = self.new_password.get_text() - - # AccountsService will automatically trigger polkit authentication - self.accountService.set_password(newpass, "") - self.destroy() - return GLib.SOURCE_REMOVE - - def set_passwords_visibility(self): - visible = self.show_password.get_active() - self.new_password.set_visibility(visible) - self.confirm_password.set_visibility(visible) - - def _on_new_password_icon_released(self, widget, icon_pos, event): - self.infobar.hide() - self.show_password.set_active(True) - characters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-" - newpass = "" - for i in range (8): - index = randint(0, len(characters) -1) - newpass = newpass + characters[index] - - self.new_password.set_text(newpass) - self.confirm_password.set_text(newpass) - self.check_passwords() - - def _on_show_password_toggled(self, widget): - self.set_passwords_visibility() - - # Based on setPasswordStrength() in Mozilla Seamonkey, which is tri-licensed under MPL 1.1, GPL 2.0, and LGPL 2.1. - # Forked from Ubiquity validation.py - def password_strength(self, password): - upper = lower = digit = symbol = 0 - for char in password: - if char.isdigit(): - digit += 1 - elif char.islower(): - lower += 1 - elif char.isupper(): - upper += 1 - else: - symbol += 1 - length = len(password) - - length = min(length,4) - digit = min(digit,3) - upper = min(upper,3) - symbol = min(symbol,3) - strength = ( - ((length * 0.1) - 0.2) + - (digit * 0.1) + - (symbol * 0.15) + - (upper * 0.1)) - if strength > 1: - strength = 1 - if strength < 0: - strength = 0 - return strength - - def _on_passwords_changed(self, widget): - self.infobar.hide() - new_password = self.new_password.get_text() - confirm_password = self.confirm_password.get_text() - strength = self.password_strength(new_password) - if new_password != confirm_password: - self.confirm_password.set_icon_from_stock(Gtk.EntryIconPosition.SECONDARY, Gtk.STOCK_DIALOG_WARNING) - self.confirm_password.set_icon_tooltip_text(Gtk.EntryIconPosition.SECONDARY, _("Passwords do not match")) - self.confirm_password.set_tooltip_text(_("Passwords do not match")) - else: - self.confirm_password.set_icon_from_stock(Gtk.EntryIconPosition.SECONDARY, None) - self.confirm_password.set_tooltip_text("") - if len(new_password) < 8: - self.strengh_label.set_text(_("Too short")) - self.strengh_indicator.set_fraction(0.0) - elif strength < 0.6: - self.strengh_label.set_text(_("Weak")) - self.strengh_indicator.set_fraction(0.2) - elif strength < 0.75: - self.strengh_label.set_text(_("Fair")) - self.strengh_indicator.set_fraction(0.4) - elif strength < 0.9: - self.strengh_label.set_text(_("Good")) - self.strengh_indicator.set_fraction(0.6) - else: - self.strengh_label.set_text(_("Strong")) - self.strengh_indicator.set_fraction(1.0) - - self.check_passwords() - - def check_passwords(self): - new_password = self.new_password.get_text() - confirm_password = self.confirm_password.get_text() - if len(new_password) >= 8 and new_password == confirm_password: - self.set_response_sensitive(Gtk.ResponseType.OK, True) - else: - self.set_response_sensitive(Gtk.ResponseType.OK, False) From 0696c99d64755c223c53ce9c25bf111933e6d8f0 Mon Sep 17 00:00:00 2001 From: Michael Webster Date: Thu, 4 Dec 2025 20:47:33 -0500 Subject: [PATCH 3/3] ChangePasswordDialog: Use xsi-symbolic for icons, stop using stock ids for buttons. Stock ids give you fullcolor icons if you have "Show icons on buttons" enabled. The replacement strings are already used elsewhere so no new translations. Add dim-label style class to the entry labels to match the user Add dialog. --- .../cinnamon-settings/bin/ChangePasswordDialog.py | 8 +++++--- .../share/cinnamon/cinnamon-settings/modules/cs_user.py | 5 ++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/files/usr/share/cinnamon/cinnamon-settings/bin/ChangePasswordDialog.py b/files/usr/share/cinnamon/cinnamon-settings/bin/ChangePasswordDialog.py index f6438878a7..b8503e7236 100644 --- a/files/usr/share/cinnamon/cinnamon-settings/bin/ChangePasswordDialog.py +++ b/files/usr/share/cinnamon/cinnamon-settings/bin/ChangePasswordDialog.py @@ -28,15 +28,17 @@ def __init__ (self, accountsUser, password_mask_label=None, group_mask_label=Non table.set_col_spacings(8) label = Gtk.Label(label=_("New password"), halign=Gtk.Align.END) + label.get_style_context().add_class("dim-label") label.set_alignment(1, 0.5) table.attach(label, 0, 1, 0, 1, xoptions=Gtk.AttachOptions.FILL) label = Gtk.Label(label=_("Confirm password"), halign=Gtk.Align.END) + label.get_style_context().add_class("dim-label") label.set_alignment(1, 0.5) table.attach(label, 0, 1, 2, 3, xoptions=Gtk.AttachOptions.FILL) self.new_password = Gtk.Entry() - self.new_password.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, "view-refresh-symbolic") + self.new_password.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, "xsi-view-refresh-symbolic") self.new_password.set_icon_tooltip_text(Gtk.EntryIconPosition.SECONDARY, _("Generate a password")) self.new_password.set_tooltip_text(_("Generate a password")) self.new_password.connect("icon-release", self._on_new_password_icon_released) @@ -75,7 +77,7 @@ def __init__ (self, accountsUser, password_mask_label=None, group_mask_label=Non content.add(self.infobar_label) table.attach(self.infobar, 0, 3, 4, 5) - self.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, _("Change"), Gtk.ResponseType.OK) + self.add_buttons(_("Cancel"), Gtk.ResponseType.CANCEL, _("Change"), Gtk.ResponseType.OK) self.set_passwords_visibility() self.set_response_sensitive(Gtk.ResponseType.OK, False) @@ -169,7 +171,7 @@ def _on_passwords_changed(self, widget): confirm_password = self.confirm_password.get_text() strength = self.password_strength(new_password) if new_password != confirm_password: - self.confirm_password.set_icon_from_stock(Gtk.EntryIconPosition.SECONDARY, Gtk.STOCK_DIALOG_WARNING) + self.confirm_password.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, "xsi-dialog-warning-symbolic") self.confirm_password.set_icon_tooltip_text(Gtk.EntryIconPosition.SECONDARY, _("Passwords do not match")) self.confirm_password.set_tooltip_text(_("Passwords do not match")) else: diff --git a/files/usr/share/cinnamon/cinnamon-settings/modules/cs_user.py b/files/usr/share/cinnamon/cinnamon-settings/modules/cs_user.py index 4bef4cd9c0..de76f1f686 100755 --- a/files/usr/share/cinnamon/cinnamon-settings/modules/cs_user.py +++ b/files/usr/share/cinnamon/cinnamon-settings/modules/cs_user.py @@ -162,7 +162,10 @@ def _on_face_photo_menuitem_activated(self, menuitem): def _on_face_browse_menuitem_activated(self, menuitem): - dialog = Gtk.FileChooserDialog(None, None, Gtk.FileChooserAction.OPEN, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) + dialog = Gtk.FileChooserDialog(None, None, Gtk.FileChooserAction.OPEN, + (_("Cancel"), Gtk.ResponseType.CANCEL, + _("Open"), Gtk.ResponseType.OK) + ) dialog.set_current_folder(self.accountService.get_home_dir()) filter = Gtk.FileFilter() filter.set_name(_("Images"))