-
Notifications
You must be signed in to change notification settings - Fork 685
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Force journalists to use diceware passwords #1509
Force journalists to use diceware passwords #1509
Conversation
1c476de
to
502237e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pretty good overall. I think we can get this one merged after one round of review if you can make the changes below and in-line, as well as add some new specific tests + fix existing ones.
- Even though a call to
crypto_util.genrandomid(7)
will never produce a password of greater than 128 characters, it seems sensible to still check generated passwords against this value as a defense against future regressions. s/KeePass/KeePassX\ database/g
. Also, adding the word "immediately" before save, and maybe even putting "immediately save this new password to your KeePassX database" in bold would help ensure users/admins do not lose their password... Or, we can instruct users to copy the passphrase to their KeePassX database and then copy it from there back into the confirmation prompt--this goes for both ways a password is changed (see Prevent automated submissions (CAPTCHA) #3).- There is an edge case where the server undergoes a regular reboot or otherwise loses connection to the client after a password change via
admin_edit_user
. It is possible the password is changed, but the flashed message never makes it back to the admin, effectively locking everyone out of the account. Having a confirmation such as how the user is forced to retype their password innew_password
is way to prevent this. - I'm not sure "encrypted USB" should be the go-to for transferring passwords. If the admin and journo see each other in person, Diceware passphrases can just be copied by hand w/o introducing another USB and assoc. risks. If they don't see each other in person regularly, then we'll need to recommend an alternative (let's discuss!).
- In some of your try/except blocks you put additional statements and/or expressions in the try block than you are trying to protect (e.g.,
flash
). A better place for these is anelse
block (see https://docs.python.org/3/tutorial/errors.html#handling-exceptions).
securedrop/db.py
Outdated
# Enforce a reasonable maximum length for passwords to avoid DoS | ||
if len(password) > self.MAX_PASSWORD_LEN: | ||
if len(password) > Journalist.MAX_PASSWORD_LEN: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we're referencing a class attribute here, it makes sense to make this a classmethod instead of a staticmethod.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this does need to be static so it can be preemptively used without creating a Journalist instance. As in, we check the password N times, then make one instance and set it.
Or is that not a good approach?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this does need to be static so it can be preemptively used without creating a Journalist instance.
You can do the same thing with a class method. The main advantage in this case is that, by writing cls.MAX_PASSWORD_LEN
, even if the Journalist
class where to change names, the method would still work w/o requiring you to replace Journalist
in the method body with the new class name. In this case it's unlikely we'll change the name of the Journalist
class, but I still think it's good practice. This SO answer http://stackoverflow.com/a/12179752 might give a little more info. on when it's better to use one or the other.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes, you're totally right.
@@ -214,7 +214,12 @@ class BadTokenException(Exception): | |||
"""Raised when a user logins in with an incorrect TOTP token""" | |||
|
|||
|
|||
class InvalidPasswordLength(Exception): | |||
class PasswordError(Exception): | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I'd get rid of the empty line here and add a period to the docstring.
securedrop/journalist.py
Outdated
flash("Passwords didn't match", "error") | ||
while True: | ||
password = crypto_util.genrandomid(7) | ||
if len(password) > Journalist.MAX_PASSWORD_LEN: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should use Journalist.check_password_acceptable
here the same way you did in manage.py
.
securedrop/manage.py
Outdated
continue | ||
|
||
if password == password_again: | ||
while True: | ||
print("This journalist's password is: {}".format(password)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently manage.py
is only used to create admin accounts. So your message about both the admin and the Journalist storing this entry doesn't make sense in this context.
securedrop/journalist.py
Outdated
@@ -306,24 +306,25 @@ def edit_account(): | |||
user = g.user | |||
|
|||
if request.method == 'POST': |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no more new_password
form in edit_account.html
(instead you made a new view), so you can basically get rid of the entire body of edit_account
except the return
statement. And you'll want to remove POST
from the app.route
decorator as well.
@fowlslegs In reply to 3, the edge case about a reboot / server failure between the password being changed and the flashed message being send to the client, I think the best thing to do would be to redirect a confirmation page before changing the password (but persist all the other changes). The admin would have to click "yes, confirm password change" on a page with text saying "this is the new password, here's where to save it." Or something to that effect. Then the password change is persisted, and a confirmation is flashed. Done. I'm only asking because it seems like UI/UX is now becoming a priority, and I figured I should check in before making this change. @ninavizz might want to give input here too. |
Maybe it is possible to use a JS |
502237e
to
ceb4a40
Compare
@fowlslegs: I made the changes. One thing doesn't work. I'm trying to use the JS to add a field to the form, but for the life of me I can't get it to work. Do you think you could take a peek? i think everything else works here. Edit: Just kidding. Got it. :D |
047d4a3
to
7856d37
Compare
If you think this is ready can you fix the existing tests and write new ones to thoroughly test the new functionality? |
1489654
to
7f5a0bc
Compare
Hey @fowlslegs, can i get some assistance with Selenium. Right now I have correct behavior in the browser, but I can't get it to work in the functional tests. Right now, I'm dying at |
7f5a0bc
to
45fe083
Compare
Just kidding. Nailed it. :D |
Nice! I'll try to re-review Friday. Prob too busy tomorrow. |
2594850
to
303a244
Compare
@fowlslegs Whenever you get around to checking this, there was an error on the VM:
I think restarting the build should get this green. |
Build restarted! |
5fb0e56
to
54c93c0
Compare
@fowlslegs I'm rebasing this at the moment and think there might be a better solution than the JS |
@heartsucker Don't feel annoying pinging me if it's been a couple of days after I said I would review and I haven't. Everyone forgets. Anyway, to answer your question, sounds like a good idea to me! Ping me when you've got that in and then I'll finally review. |
@fowlslegs No worries. I was busy with other tickets and would have been just endlessly rebasing this. :) |
54c93c0
to
e302bc7
Compare
5c084ca
to
6c0e045
Compare
Note: the function |
6c0e045
to
310b922
Compare
securedrop/journalist.py
Outdated
@@ -188,13 +184,10 @@ def admin_add_user(): | |||
otp_secret=otp_secret) | |||
db_session.add(new_user) | |||
db_session.commit() | |||
except InvalidPasswordLength: | |||
except PasswordError: | |||
flash('There was an error with the autogenerated passoword. ' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
passoword
-> password
310b922
to
00bc3dc
Compare
Looks like a bug has crept in - I'm trying to make an admin from the command line and I get:
(Relevant: #1982) |
00bc3dc
to
b85bffc
Compare
Fixed the bug. Travis should be happy in a minute. |
Cool - taking another look! Also writing out here to summarize our discussion in person: since we've had a long back and forth in this PR, once the basic functionality and sufficient testing is down, I'm going to make two followup issues: one for design/UI cleanup and one for documentation. Note that we should tackle updating the documentation today when this is merged (even without updated screenshots, which we can do prior to release) so that any developers trying to work on SecureDrop are not confused by this new functionality. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approving this! Thanks for bearing with us on the long back and forth here @heartsucker . This is a solid improvement and I'm going to merge this in and any modifications we can do in a subsequent PR. I know the documentation updates were not added here, but I've made followup #2167 for that and we should get that done and merged in today.
Staging failure looks like a flake, restarting |
Note: Coverage is decreasing here because @heartsucker has removed |
In PR #2141, we added a flag in pytest to generate automatic screenshots for each page of the source and journalist interface in each translated language. At the same time we merged in #1509 which automatically generated diceware passphrases. Unfortunately there was a lingering reference to a password (that is now automatically generated) in one of the functions used to generate the screenshots, which caused two test failures.
In PR #2141, we added a flag in pytest to generate automatic screenshots for each page of the source and journalist interface in each translated language. At the same time we merged in #1509 which automatically generated diceware passphrases. Unfortunately there was a lingering reference to a password (that is now automatically generated) in one of the functions used to generate the screenshots, which caused two test failures.
In PR #2141, we added a flag in pytest to generate automatic screenshots for each page of the source and journalist interface in each translated language. At the same time we merged in #1509 which automatically generated diceware passphrases. Unfortunately there was a lingering reference to a password (that is now automatically generated) in one of the functions used to generate the screenshots, which caused two test failures.
In PR #2141, we added a flag in pytest to generate automatic screenshots for each page of the source and journalist interface in each translated language. At the same time we merged in #1509 which automatically generated diceware passphrases. Unfortunately there was a lingering reference to a password (that is now automatically generated) in one of the functions used to generate the screenshots, which caused two test failures.
In PR #2141, we added a flag in pytest to generate automatic screenshots for each page of the source and journalist interface in each translated language. At the same time we merged in #1509 which automatically generated diceware passphrases. Unfortunately there was a lingering reference to a password (that is now automatically generated) in one of the functions used to generate the screenshots, which caused two test failures.
This is the initial work for fixing #980 (superseding #1493). Forces journalists to use auto-generated diceware passwords and does not allow user input for passwords.
This PR is not ready for merge, but is here for initial functionality review before fixing all of the unit/functional tests.