From 8c19c8d25b3ee79e7280fc328bcb6059bd0e1d9d Mon Sep 17 00:00:00 2001 From: Guillaume Vincent Date: Thu, 23 Feb 2023 18:42:45 +0100 Subject: [PATCH] Migrate numbers to digits everywhere --- cli/lesspass/__init__.py | 2 +- .../0011_rename_numbers_password_digits.py | 16 +++++ containers/backend/api/models.py | 2 +- containers/backend/api/serializers.py | 12 +++- containers/backend/api/tests/tests_passwords.py | 81 +++++++++++++++++----- doc/openapi.yaml | 6 +- mobile/src/password/passwordGenerator.test.js | 1 - mobile/src/password/profilesReducer.js | 24 +++++-- mobile/src/password/profilesReducer.test.js | 10 +-- packages/lesspass-pure/src/components/Options.vue | 2 +- packages/lesspass-pure/src/i18n/en.json | 2 +- packages/lesspass-pure/src/services/url-parser.js | 22 +++--- .../lesspass-pure/src/store/defaultPassword.js | 2 +- .../src/views/ExportYourPasswords.vue | 1 - .../lesspass-pure/src/views/PasswordGenerator.vue | 53 +++++++------- .../tests/unit/services/url-parser.spec.js | 8 +-- .../lesspass-pure/tests/unit/store/getters.spec.js | 4 +- .../tests/unit/store/mutations.spec.js | 12 ++-- packages/lesspass-site/index.html | 2 +- packages/lesspass/test.js | 2 +- 20 files changed, 172 insertions(+), 92 deletions(-) create mode 100644 containers/backend/api/migrations/0011_rename_numbers_password_digits.py diff --git a/cli/lesspass/__init__.py b/cli/lesspass/__init__.py index 7afc036..6442b00 100644 --- a/cli/lesspass/__init__.py +++ b/cli/lesspass/__init__.py @@ -31,7 +31,7 @@ Options: --no-symbols remove symbols from password -c, --clipboard copy generated password to clipboard rather than displaying it. Need pbcopy (OSX), xsel or xclip (Linux) or clip (Windows). - -v, --version lesspass version number + -v, --version lesspass version Examples: diff --git a/containers/backend/api/migrations/0011_rename_numbers_password_digits.py b/containers/backend/api/migrations/0011_rename_numbers_password_digits.py new file mode 100644 index 0000000..ee9c0f9 --- /dev/null +++ b/containers/backend/api/migrations/0011_rename_numbers_password_digits.py @@ -0,0 +1,16 @@ +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("api", "0010_alter_password_site_and_login"), + ] + + operations = [ + migrations.RenameField( + model_name="password", + old_name="numbers", + new_name="digits", + ), + ] diff --git a/containers/backend/api/models.py b/containers/backend/api/models.py index b70b298..09538e8 100644 --- a/containers/backend/api/models.py +++ b/containers/backend/api/models.py @@ -78,7 +78,7 @@ class Password(DateMixin): lowercase = models.BooleanField(default=True) uppercase = models.BooleanField(default=True) symbols = models.BooleanField(default=True) - numbers = models.BooleanField(default=True) + digits = models.BooleanField(default=True) length = models.IntegerField(default=16) counter = models.IntegerField(default=1) diff --git a/containers/backend/api/serializers.py b/containers/backend/api/serializers.py index 88bd79d..4eca3da 100644 --- a/containers/backend/api/serializers.py +++ b/containers/backend/api/serializers.py @@ -13,7 +13,7 @@ class PasswordSerializer(serializers.ModelSerializer): "lowercase", "uppercase", "symbols", - "numbers", + "digits", "counter", "length", "version", @@ -26,6 +26,16 @@ class PasswordSerializer(serializers.ModelSerializer): user = self.context["request"].user return models.Password.objects.create(user=user, **validated_data) + def to_internal_value(self, data): + if "number" in data and "digits" not in data: + data["digits"] = data["number"] + if "numbers" in data and "digits" not in data: + data["digits"] = data["numbers"] + if "symbol" in data and "symbols" not in data: + data["symbols"] = data["symbol"] + data = super().to_internal_value(data) + return data + class EncryptedPasswordProfileSerializer(serializers.ModelSerializer): class Meta: diff --git a/containers/backend/api/tests/tests_passwords.py b/containers/backend/api/tests/tests_passwords.py index a19675f..5e5a9fa 100644 --- a/containers/backend/api/tests/tests_passwords.py +++ b/containers/backend/api/tests/tests_passwords.py @@ -45,53 +45,98 @@ class LoginPasswordsTestCase(APITestCase): self.assertEqual(404, request.status_code) self.assertEqual(1, models.Password.objects.all().count()) - def test_create_password(self): + def test_create_password_old_api(self): password = { "site": "lesspass.com", - "login": "test@oslab.fr", - "lowercase": True, + "login": "test@lesspass.com", + "lowercase": False, "uppercase": True, - "number": True, - "symbol": True, - "counter": 1, + "numbers": False, + "symbols": False, + "counter": 2, "length": 12, } self.assertEqual(0, models.Password.objects.count()) self.client.post("/api/passwords/", password) self.assertEqual(1, models.Password.objects.count()) + profile = models.Password.objects.first() + self.assertEqual(profile.site, "lesspass.com") + self.assertEqual(profile.login, "test@lesspass.com") + self.assertFalse(profile.lowercase) + self.assertTrue(profile.uppercase) + self.assertFalse(profile.digits) + self.assertFalse(profile.symbols) + self.assertEqual(profile.counter, 2) + self.assertEqual(profile.length, 12) + self.assertEqual(profile.version, 2) - def test_create_password_v2(self): + def test_create_password_with_missing_s_old_api(self): password = { "site": "lesspass.com", - "login": "test@oslab.fr", + "login": "test@lesspass.com", "lowercase": True, "uppercase": True, - "number": True, - "symbol": True, + "number": False, + "symbol": False, "counter": 1, - "length": 12, + "length": 16, "version": 2, } self.client.post("/api/passwords/", password) - self.assertEqual(2, models.Password.objects.first().version) + profile = models.Password.objects.first() + self.assertFalse(profile.digits) + self.assertFalse(profile.symbols) + + def test_create_password_v2(self): + password = { + "site": "lesspass.com", + "login": "testv2@lesspass.com", + "lowercase": True, + "uppercase": False, + "digits": False, + "symbols": False, + "counter": 3, + "length": 16, + "version": 2, + } + self.client.post("/api/passwords/", password) + profile = models.Password.objects.first() + self.assertEqual(profile.site, "lesspass.com") + self.assertEqual(profile.login, "testv2@lesspass.com") + self.assertTrue(profile.lowercase) + self.assertFalse(profile.uppercase) + self.assertFalse(profile.digits) + self.assertFalse(profile.symbols) + self.assertEqual(profile.counter, 3) + self.assertEqual(profile.length, 16) + self.assertEqual(profile.version, 2) def test_update_password(self): password = factories.PasswordFactory(user=self.user) self.assertNotEqual("facebook.com", password.site) new_password = { "site": "facebook.com", - "login": "test@oslab.fr", + "login": "test@lesspass.com", "lowercase": True, "uppercase": True, - "number": True, - "symbol": True, - "counter": 1, - "length": 12, + "digits": False, + "symbols": False, + "counter": 2, + "length": 20, + "version": 2, } request = self.client.put("/api/passwords/%s/" % password.id, new_password) self.assertEqual(200, request.status_code, request.content.decode("utf-8")) password_updated = models.Password.objects.get(id=password.id) - self.assertEqual("facebook.com", password_updated.site) + self.assertEqual(password_updated.site, "facebook.com") + self.assertEqual(password_updated.login, "test@lesspass.com") + self.assertTrue(password_updated.lowercase) + self.assertTrue(password_updated.uppercase) + self.assertFalse(password_updated.digits) + self.assertFalse(password_updated.symbols) + self.assertEqual(password_updated.counter, 2) + self.assertEqual(password_updated.length, 20) + self.assertEqual(password_updated.version, 2) def test_cant_update_other_password(self): not_my_password = factories.PasswordFactory(user=factories.UserFactory()) diff --git a/doc/openapi.yaml b/doc/openapi.yaml index bd8c8e2..630cdb8 100644 --- a/doc/openapi.yaml +++ b/doc/openapi.yaml @@ -356,7 +356,7 @@ components: - site - uppercase - lowercase - - numbers + - digits - symbols - length - counter @@ -378,8 +378,8 @@ components: description: Generated password has lowercase characters type: boolean default: true - numbers: - description: Generated password has numbers + digits: + description: Generated password has digits type: boolean default: true symbols: diff --git a/mobile/src/password/passwordGenerator.test.js b/mobile/src/password/passwordGenerator.test.js index 185c159..2294b13 100644 --- a/mobile/src/password/passwordGenerator.test.js +++ b/mobile/src/password/passwordGenerator.test.js @@ -51,7 +51,6 @@ describe("generatePassword should not care about the extra number field used for lowercase: true, uppercase: true, digits: true, - number: true, symbols: true, }; return generatePassword("password", passwordProfile).then( diff --git a/mobile/src/password/profilesReducer.js b/mobile/src/password/profilesReducer.js index c2e8105..0aee37c 100644 --- a/mobile/src/password/profilesReducer.js +++ b/mobile/src/password/profilesReducer.js @@ -3,13 +3,23 @@ const initialState = {}; export default function reduce(state = initialState, action) { switch (action.type) { case "SET_PASSWORD_PROFILES": - return action.profiles.reduce((acc, profile) => { - acc[profile.id] = { - ...profile, - ["digits"]: profile.numbers, - }; - return acc; - }, {}); + return action.profiles + .map((profile) => { + if ("numbers" in profile) { + const { numbers, ...profileWithoutNumbers } = profile; + return { + ...profileWithoutNumbers, + digits: numbers, + }; + } + return profile; + }) + .reduce((acc, p) => { + acc[p.id] = { + ...p, + }; + return acc; + }, {}); case "ADD_PASSWORD_PROFILE": return { ...state, diff --git a/mobile/src/password/profilesReducer.test.js b/mobile/src/password/profilesReducer.test.js index 2506fec..0976457 100644 --- a/mobile/src/password/profilesReducer.test.js +++ b/mobile/src/password/profilesReducer.test.js @@ -31,16 +31,16 @@ describe("profiles reducer", () => { } ) ).toEqual({ - p1: { id: "p1", numbers: true, digits: true }, - p2: { id: "p2", numbers: false, digits: false }, + p1: { id: "p1", digits: true }, + p2: { id: "p2", digits: false }, }); }); it("REMOVE_PASSWORD_PROFILE", () => { expect( reducer( { - p1: { id: "p1", numbers: true, digits: true }, - p2: { id: "p2", numbers: false, digits: false }, + p1: { id: "p1", digits: true }, + p2: { id: "p2", digits: false }, }, { type: "REMOVE_PASSWORD_PROFILE", @@ -48,7 +48,7 @@ describe("profiles reducer", () => { } ) ).toEqual({ - p2: { id: "p2", numbers: false, digits: false }, + p2: { id: "p2", digits: false }, }); }); it("ADD_PASSWORD_PROFILE", () => { diff --git a/packages/lesspass-pure/src/components/Options.vue b/packages/lesspass-pure/src/components/Options.vue index 24efc74..ddbc316 100644 --- a/packages/lesspass-pure/src/components/Options.vue +++ b/packages/lesspass-pure/src/components/Options.vue @@ -54,7 +54,7 @@ type="checkbox" tabindex="1" class="form-check-input" - v-model="options.numbers" + v-model="options.digits" />