Commit Graph

109 Commits

Author SHA1 Message Date
stvnrlly
d45cbb2c86 util for naive dt parsing 2022-11-28 20:40:53 -05:00
stvnrlly
e12e780a05 code cleanup 2022-11-28 15:53:56 -05:00
stvnrlly
8bd1285bfa more test fixes 2022-11-28 10:48:51 -05:00
stvnrlly
46723b6c11 initial timezone pass, which breaks many tests 2022-11-22 12:00:29 -05:00
Chris Hill-Scott
c6dc0d513e Allow editing of pending users
At the moment if a user is pending we don’t show the ‘change’ link.

This is unhelpful because:
- there’s no way to remove this user
- there’s no way to change their phone number, if the reason that
  they are still pending is because they’ve been unable to receive
  the two factor code at the number they first provided
2022-05-05 09:42:14 +01:00
Chris Hill-Scott
c5d4bfd8ef Refactor to avoid direct string comparison
Direct string comparison in multiple places is prone to typos. It
also means that a consumer of the class needs to know that whether
a user is pending or active is held in the `state` property, which
is an implementation detail.
2022-05-05 09:39:32 +01:00
Chris Hill-Scott
5ac6efc580 Refactor logic out of Jinja before making more complicated
To keep the conditionals in the Jinja template more readable, this
commit moves the logic into a method on the model, where it can
be split over multiple statements and lines.
2022-05-05 09:38:40 +01:00
Chris Hill-Scott
8934e402e8 Make a model collection for organisations
This makes returning a user’s organisations consistent with how we
return their services.
2021-11-09 15:05:43 +00:00
Chris Hill-Scott
cbbc58e649 Make a model collection for services
This is tidier than having a manual loop.
2021-11-09 15:05:42 +00:00
Chris Hill-Scott
9281ca7d50 Sort services and orgs in presentation layer
The model layer shouldn’t need to be concerned with sorting. For
services this means we can make a `SerialisedModelCollection` rather
than writing a manual loop.
2021-11-09 15:05:42 +00:00
Chris Hill-Scott
4d4c9c0db2 Make services natively sortable 2021-11-09 15:05:42 +00:00
Chris Hill-Scott
5c1920fc20 Remove old method of updating email_access_validated_at
Previously we were passing a flag to the API which handled this. Now
we are doing it at the time of clicking the link, not at the time of
storing the new password. We don’t need to update the timestamp twice,
so this commit removes the code which tells the API to do it.
2021-08-19 11:14:47 +01:00
Chris Hill-Scott
cb59413581 Update email_access_validated_at on link click
When someone uses a fresh password reset link they have proved that they
have access to their inbox.

At the moment, when revalidating a user’s email address we wait until
after they’ve put in the 2FA code before updating the timestamp which
records when they last validated their email address[1].

We can’t think of a good reason that we need the extra assurance of a
valid 2FA code to assert that the user has access to their email –
they’ve done that just by clicking the link. When the user clicks the
link we already update their failed login count before they 2fa. Think
it makes sense to handle `email_access_validated_at` then too.

As a bonus, the functional tests never go as far as getting a 2FA code
after a password reset[2], so the functional test user never gets its
timestamp updated. This causes the functional tests start failing after
90 days. By moving the update to this point we ensure that the
functional tests will keep passing indefinitely.

1. This code in the API (91542ad33e/app/dao/users_dao.py (L131))
   which is called by this code in the admin app (9ba37249a4/app/utils/login.py (L26))
2. 5837eb01dc/tests/functional/preview_and_dev/test_email_auth.py (L43-L46)
2021-08-19 11:14:47 +01:00
Ben Thorner
dcfff87cc0 Continue to remove "roles" terminology
This renames the two functions we have to translate between UI and
DB permissions, as well as some of their associated variables to
make it clearer which kind of permission they contain.
2021-07-28 12:37:17 +01:00
Ben Thorner
ba9865e62e Start to remove use of the term "roles"
We don't use this term consistently and it's not defined anywhere.
Since most of the Admin app deals with user-facing permssions, it's
OK to just use the term "permissions". Where both types of permission
are present in the same file, we can more clearly distinguish them
as "UI permissions" and "DB permissions".
2021-07-28 12:37:16 +01:00
Ben Thorner
1127a03c32 Move and rename roles_and_permissions.py
This file does not represent a model, but rather a set of utilities
that are specific to user permissions (vs. service permissions).
2021-07-28 12:36:40 +01:00
Ben Thorner
832422fc66 Replace "admin roles" with "ui permissions"
In response to: [1].

While this does introduce a new term ("admin roles" is still used
elsewhere in the code), I plan to fix this in a follow-up PR (it
turned out to be quite a big change to do on this branch).

[1]: https://github.com/alphagov/notifications-admin/pull/3970#discussion_r673292339
2021-07-21 16:19:56 +01:00
Ben Thorner
9fafc092f7 Audit permissions when adding a user to a service
This is useful information to store for the event, which would be
lost if someone subsequently changed them.

Rather than updating lots of mock assertions, I've replaced them
with a single test / assert at a lower level, which is consistent
with auditing being a non-critical function.
2021-07-21 15:32:04 +01:00
Ben Thorner
171f911237 Audit when user permissions are changed
I've used the term "admin_roles" in the event data to try and show
that these are not the permissions we store in the DB. This is the
name we use for the abstracted form of permissions in the Admin app.
While we could store the DB permissions, that would be a bit more
effort and arguably it's clearer to keep the event data consistent
with the options the user actually saw / chose.
2021-07-21 15:32:03 +01:00
Ben Thorner
2241b119b0 Split (has_)permissions_for_service method
This avoids duplicating the code to get user permissions ("admin
roles") for a service, which we'll need in the next commit.
2021-07-21 15:32:02 +01:00
Ben Thorner
0f87ffe093 Move inline import to top of file
Usually we have imports at the top. It looks like the reason for
them being inline was to avoid a circular import, but we can also
avoid this by not importing everything from the app module.

Since we're about to add more imports from event_handlers, now is
a good time to refactor them. Note this matches how we import the
event handlers in every other module.
2021-07-21 15:32:01 +01:00
Ben Thorner
e72a260e13 Merge pull request #3947 from alphagov/allow-ccs
Allow other users to use security keys
2021-07-08 11:53:03 +01:00
Ben Thorner
4c2915ce86 Use API flag to give users access to WebAuthn
This allows us to roll out the feature to other users. Note that
the flag is also "True" if the user has "webauthn_auth" as their
auth type, so this is compatible with the more fine-grained check
we have on the authentication parts of the feature. We could do a
more explicit "can_use_webauthn or webauthn_auth" check here, but
the idea is that we'll be able to get rid of this flag eventually,
so I've optimised for brevity instead.

I've modified a couple of the unhappy-path tests to make it more
explicit that the flag is false, since it can be true for Platform
Admins and "normal users" alike.
2021-07-07 15:04:48 +01:00
Leo Hemsted
7b3751240c ensure user details are always in the session after entering password
We signal that we're mid-way through the sign-in flow by adding a
`user_details` dict to the session.

previously, we'd only put a user's details in the session in `User.sign_in`,
just before sending any 2fa prompt and redirecting to the two factor
pages.

However, we found a bug where a user with no session (eg, using a fresh
browser) tried to log in, but they had never clicked the link to
validate their email address when registering. Their user's state was
still in "pending", so we redirected to `main.resend_email_verification`
as intended - however, they didn't have anything in the session and the
resend page expected to get the email address to resend to out of that.

To be safe, as soon as we've confirmed the user has entered their
password correctly, lets save the session data at that point. That way
any redirects will be fine.
2021-06-29 18:13:25 +01:00
Ben Thorner
44cf2b16b5 Merge pull request #3923 from alphagov/refactor-email-verify
Split out utils code into separate modules
2021-06-14 10:28:28 +01:00
Chris Hill-Scott
f6aa5bdfb8 Refactor User.webauthn_credentials into a ModelList
This saves a bit of repetition, and lets us attach other methods to the
collection, rather than having multiple methods on the user object
prefixed with the same name, or random functions floating about.
2021-06-09 15:21:41 +01:00
Ben Thorner
7c27646d6a Extract user utility code into own module
This provides more room for expansion, and reduces the amount of
arbitrary code in the __init__.py file for the new package.
2021-06-09 13:19:05 +01:00
Chris Hill-Scott
45645728c7 Refactor into model
It’s generally an antipattern for the view layer code to be calling the
API client directly.
2021-06-08 09:31:20 +01:00
Leo Hemsted
73a444b33a rename webauthn auth functions
_complete_webauthn_authentication -> _verify_webauthn_authentication

This function just does verification of the actual auth process -
checking the challenge is correct, the signature matches the public key
we have stored in our database, etc.

verify_webauthn_login -> _complete_webauthn_login_attempt

This function doesn't do any actual verification, we've already verified
the user is who they say they are (or not), it's about marking the
attempt, either unsuccessful (we bump the failed_login_count in the db)
or successful (we set the logged_in_at and current_session_id in the
db).

This change also informs changes to the names of methods on the user
model and in user_api_client.
2021-06-02 12:06:10 +01:00
Leo Hemsted
0ec92e8c2f DRY attested webauthn creds data 2021-06-02 12:06:10 +01:00
Leo Hemsted
92f78b14fe redirect on login; flash errors on failure
the js `fetch` function will follow redirects blindly and return you the
final 200 response. when there's an error, we don't want to go anywhere,
and we want to use the flask `flash` functionality to pop up an error
page (the likely reason for seeing this is using a yubikey that isn't
associated with your user). using `flash` and then
`window.location.reload()` handles this fine.

However, when the user does log in succesfully we need to properly log
them in - this includes:

* checking their account isn't over the max login count
* resetting failed login count to 0 if not
* setting a new session id in the database (so other browser windows are
  logged out)
* checking if they need to revalidate their email access (every 90 days)
* clearing old user out of the cache

This code all happens in the ajax function rather than being in a
separate redirect, so that you can't just navigate to the login flow. I
wasn't able to unit test that function due how it uses the session and
other flask globals, so moved the auth into its own function so it's
easy to stub out all that CBOR nonsense.

TODO: We still need to pass any `next` URLs through the chain from login
page all the way through the javascript AJAX calls and redirects to the
log_in_user function
2021-06-02 11:51:10 +01:00
Pea Tyczynska
714afff156 Merge pull request #3884 from alphagov/add-webauthn-as-auth-type
Add webauthn as an auth type
2021-05-13 14:32:03 +01:00
Pea Tyczynska
2a756f90d4 Add webauth as an auth type
When showing what auth type user uses to sign in, add a text
for users with webauthn.

On password change or sign in, throw not implemented error
if user uses webauthn auth.
2021-05-12 17:40:31 +01:00
Ben Thorner
ebb82b2e80 Add page for security keys with stubbed data
This adds a new platform admin settings row, leading a page which
shows any existing keys and allows a new one to be registered. Until
the APIs for this are implemented, the user API client just returns
some stubbed data for manual testing.

This also includes a basic JavaScript module to do the main work of
registering a new authenticator, to be implemented in the next commits.

Some more minor notes:

- Setting the headings in the mapping_table is necessary to get the
horizontal rule along the top (to match the design).

- Setting caption to False in the mapping_table is necessary to stop
an extra margin appearing at the top.
2021-05-12 13:41:53 +01:00
David McDonald
4acca3de4d Don't show user as unknown for service history
A commit was added:
600e3affc1

In it, it falls back to the string 'Unknown' for actions done by those
not belonging to the service.

This commit changes the behaviour such that if the user is not in the
list of active users for a service, it will go get the user from the DB
(or redis). This should be fine to do as redis will protect us from most
calls as most of these cases are for platform admins.

This will mean we can now see which user platform admin put a service
live rather than seeing 'Unknown'.
2021-04-06 11:36:54 +01:00
Chris Hill-Scott
6c8bfdc5b0 Refactor failed login count
We don’t vary this between different environments so it doesn’t need to
be in the config.

I was trying to look up what this value was and found it a bit confusing
that it was spread across multiple places.
2021-03-19 15:20:11 +00:00
Katie Smith
776e24a215 Merge pull request #3840 from alphagov/accounts-page-fix
Fix /accounts page to only show trial services once
2021-03-17 15:29:07 +00:00
Leo Hemsted
8a2fec6f18 stop putting invite user objects in the session
the invited_user objects can be arbitrarily large, and when we put them
in the session we risk going over the session cookie's 4kb size limit.
since https://github.com/alphagov/notifications-admin/pull/3827 was
merged, we store the user id in the session. Now that's been live for a
day or two we can safely stop putting the rich object in the session.

Needed to change a bunch of tests for this to make sure appropriate
mocks were set. Also some tests were accidentally re-using fake_uuid.

Still pop the object when cleaning up sessions. We'll need to remove
that in a future PR.
2021-03-16 18:14:04 +00:00
Katie Smith
82733d615f Delete User properties which are now no longer used 2021-03-16 15:45:21 +00:00
Leo Hemsted
45297eae43 store invited user ids in session
same as the invited org user ids in the previous commit
2021-03-15 12:21:58 +00:00
Leo Hemsted
6d62c9ba36 store invited org user ids in session
first of a two step process to remove invited user objects from the
session. we're removing them because they're of variable size, and with
a lot of folder permissions they can cause the session to exceed the 4kb
cookie size limit and not save properly.

this commit looks at invited org users only.

in this step, start saving the invited org user's id to the
session alongside the session object. Then, if the invited_org_user_id
is present in the next step of the invite flow, fetch the user object
from the API instead of from the session. If it's not present (due to a
session set by an older instance of the admin app), then just use the
old code to get the entire object out of the session.

For invites where the user is small enough to persist to the cookie,
this will still save both the old and the new way, but will always make
an extra check to the API, I think this minor performance hit is totally
fine. For invites where the user is too big to persist, they'll still
fail for now, and will need to wait until the next PR comes along and
stops saving the large invited user object to the session entirely.
2021-03-12 16:36:02 +00:00
Leo Hemsted
c89be0079a rename get_invited_user funcs
make it clear they're expecting a service/org id
2021-03-12 15:59:32 +00:00
Katie Smith
7ae4017d50 Add audit event for inviting users to a service 2021-03-08 14:34:50 +00:00
Chris Hill-Scott
89c63e3243 Remove custom getattr implementation from JSONModel
We wrote custom `__getattr__` and `__getitem__` implementation to make
it easier to find code that was accessing fields in the dictionary that
we weren’t aware of. See this commit for details:
b48305c50d

Now that no view-layer code accesses the service dictionary directly we
don’t need to support this behaviour any more. By removing it we can
make the model code simpler, and closer to the `SerialisedModel` it
inherits from.

It still needs some custom implementation because:
- a lot of our test fixtures are lazy and don’t set up all the expected
  fields, so we need to account for fields sometimes being present in
  the underlying dictionary and sometimes not
- we often implement a property that has the same name as one of the
  fields in the JSON, so we have to be careful not to try to override
  this property with the value from the underlying JSON
2020-10-19 15:52:51 +01:00
Katie Smith
895a9df55a Add confirmation banner when cancelling user invites
This shows the green banner with a tick when cancelling a user's
invitation to a service or organisation. The accessibility audit noted
that 'When cancelling an invite a new page loads, however, there is no
immediate indication that the invite has been cancelled.'

In order to display the invited user's email address as part of the
flash message, this adds new methods to the api clients for invites to get
a single invite.
2020-08-19 09:05:41 +01:00
Katie Smith
313d39415d Catch errors when user register from invite
API gives an error if it tries to add a user to a service and that user is
already a member of the service. This situation shouldn't occur - admin checks
if an invited user is a member of a service before calling API, but we
have seen this error occurring when there are two requests processing at
the same time.

This change catches the errors from API if a user is already a member of
a service and redirects the user to the service dashboard so that they
don't see an error page.
2020-05-19 13:49:17 +01:00
Leo Hemsted
85f159a25f upgrade flask_login to 0.5.0
flask_login sets a bunch of variables in the session object. We only use
one of them, `user_id`. We set that to the user id from the database,
and refer to it all over the place.

However, in flask_login 0.5.0 they prefix this with an underscore to
prevent people accidentally overwriting it etc. So when a user logs in
we need to make sure that we set user_id manually so we can still use
it.

flask_login sets a bunch of variables on the `flask.session` object.
However, this session object isn't the one that gets passed in to the
request context by flask - that one can only be modified outside of
requests from within the session_transaction context manager (see [1]).
So, flask_login populates the normal session and then we need to copy
all of those values across.

We didn't need to do this previously because we already set the
`user_id` value on line 20 of tests/__init__.py, but now that
flask_login is looking for `_user_id` instead we need to do this
properly.

[1] https://flask.palletsprojects.com/en/1.1.x/testing/#accessing-and-modifying-sessions
2020-03-13 15:16:04 +00:00
Leo Hemsted
5535db273c Revert "upgrade flask_login to 0.5.0" 2020-03-09 16:48:27 +00:00
Leo Hemsted
e4cd77a645 upgrade flask_login to 0.5.0
flask_login sets a bunch of variables in the session object. We only use
one of them, `user_id`. We set that to the user id from the database,
and refer to it all over the place.

However, in flask_login 0.5.0 they prefix this with an underscore to
prevent people accidentally overwriting it etc. So when a user logs in
we need to make sure that we set user_id manually so we can still use
it.

flask_login sets a bunch of variables on the `flask.session` object.
However, this session object isn't the one that gets passed in to the
request context by flask - that one can only be modified outside of
requests from within the session_transaction context manager (see [1]).
So, flask_login populates the normal session and then we need to copy
all of those values across.

We didn't need to do this previously because we already set the
`user_id` value on line 20 of tests/__init__.py, but now that
flask_login is looking for `_user_id` instead we need to do this
properly.

[1] https://flask.palletsprojects.com/en/1.1.x/testing/#accessing-and-modifying-sessions
2020-03-09 12:04:17 +00:00
Pea Tyczynska
3a93fe6892 Fix reset password flow
It was broken because of mismatch in update password argument
2020-02-18 14:50:27 +00:00