2024-08-12 21:17:08 -05:00
|
|
|
# -*- coding: utf-8; -*-
|
|
|
|
|
|
|
|
|
|
from unittest.mock import patch
|
|
|
|
|
|
|
|
|
|
from sqlalchemy import orm
|
|
|
|
|
|
|
|
|
|
import colander
|
|
|
|
|
|
2025-08-09 09:56:00 -05:00
|
|
|
from wuttaweb.grids import Grid
|
2024-08-13 10:52:30 -05:00
|
|
|
from wuttaweb.views import users as mod
|
2025-12-28 22:48:36 -06:00
|
|
|
from wuttaweb.testing import WebTestCase, FunctionalTestCase
|
2024-08-12 21:17:08 -05:00
|
|
|
|
|
|
|
|
|
2024-08-13 10:52:30 -05:00
|
|
|
class TestUserView(WebTestCase):
|
2024-08-12 21:17:08 -05:00
|
|
|
|
|
|
|
|
def make_view(self):
|
2024-08-13 10:52:30 -05:00
|
|
|
return mod.UserView(self.request)
|
2024-08-12 21:17:08 -05:00
|
|
|
|
2024-08-14 15:55:10 -05:00
|
|
|
def test_includeme(self):
|
2025-08-31 12:26:43 -05:00
|
|
|
self.pyramid_config.include("wuttaweb.views.users")
|
2024-08-14 15:55:10 -05:00
|
|
|
|
2024-08-12 21:17:08 -05:00
|
|
|
def test_get_query(self):
|
|
|
|
|
view = self.make_view()
|
|
|
|
|
query = view.get_query(session=self.session)
|
|
|
|
|
self.assertIsInstance(query, orm.Query)
|
|
|
|
|
|
|
|
|
|
def test_configure_grid(self):
|
|
|
|
|
model = self.app.model
|
|
|
|
|
view = self.make_view()
|
|
|
|
|
grid = view.make_grid(model_class=model.User)
|
2025-08-31 12:26:43 -05:00
|
|
|
self.assertFalse(grid.is_linked("person"))
|
2024-08-12 21:17:08 -05:00
|
|
|
view.configure_grid(grid)
|
2025-08-31 12:26:43 -05:00
|
|
|
self.assertTrue(grid.is_linked("person"))
|
2024-08-12 21:17:08 -05:00
|
|
|
|
2024-08-23 14:14:41 -05:00
|
|
|
def test_grid_row_class(self):
|
|
|
|
|
model = self.app.model
|
2025-08-31 12:26:43 -05:00
|
|
|
user = model.User(username="barney", active=True)
|
2024-08-23 14:14:41 -05:00
|
|
|
data = dict(user)
|
|
|
|
|
view = self.make_view()
|
|
|
|
|
|
|
|
|
|
self.assertIsNone(view.grid_row_class(user, data, 1))
|
|
|
|
|
|
|
|
|
|
user.active = False
|
2025-08-31 12:26:43 -05:00
|
|
|
self.assertEqual(view.grid_row_class(user, data, 1), "has-background-warning")
|
2024-08-23 14:14:41 -05:00
|
|
|
|
2024-11-24 17:19:50 -06:00
|
|
|
def test_is_editable(self):
|
|
|
|
|
model = self.app.model
|
|
|
|
|
view = self.make_view()
|
|
|
|
|
|
|
|
|
|
# active user is editable
|
2025-08-31 12:26:43 -05:00
|
|
|
user = model.User(username="barney", active=True)
|
2024-11-24 17:19:50 -06:00
|
|
|
self.assertTrue(view.is_editable(user))
|
|
|
|
|
|
|
|
|
|
# inactive also editable
|
2025-08-31 12:26:43 -05:00
|
|
|
user = model.User(username="barney", active=False)
|
2024-11-24 17:19:50 -06:00
|
|
|
self.assertTrue(view.is_editable(user))
|
|
|
|
|
|
|
|
|
|
# but not if prevent_edit flag is set
|
2025-08-31 12:26:43 -05:00
|
|
|
user = model.User(username="barney", prevent_edit=True)
|
2024-11-24 17:19:50 -06:00
|
|
|
self.assertFalse(view.is_editable(user))
|
|
|
|
|
|
|
|
|
|
# unless request user is root
|
|
|
|
|
self.request.is_root = True
|
|
|
|
|
self.assertTrue(view.is_editable(user))
|
|
|
|
|
|
2024-08-12 21:17:08 -05:00
|
|
|
def test_configure_form(self):
|
|
|
|
|
model = self.app.model
|
2025-08-31 12:26:43 -05:00
|
|
|
person = model.Person(
|
|
|
|
|
first_name="Barney", last_name="Rubble", full_name="Barney Rubble"
|
|
|
|
|
)
|
|
|
|
|
barney = model.User(username="barney", person=person)
|
2024-08-13 21:43:56 -05:00
|
|
|
self.session.add(barney)
|
|
|
|
|
self.session.commit()
|
2024-08-12 21:17:08 -05:00
|
|
|
view = self.make_view()
|
2024-08-13 21:43:56 -05:00
|
|
|
|
2025-01-27 17:07:42 -06:00
|
|
|
# person replaced with first/last name when creating or editing
|
2025-08-31 12:26:43 -05:00
|
|
|
with patch.object(view, "viewing", new=True):
|
2025-01-27 17:07:42 -06:00
|
|
|
form = view.make_form(model_instance=barney)
|
2025-08-31 12:26:43 -05:00
|
|
|
self.assertIn("person", form)
|
|
|
|
|
self.assertNotIn("first_name", form)
|
|
|
|
|
self.assertNotIn("last_name", form)
|
2025-01-27 17:07:42 -06:00
|
|
|
view.configure_form(form)
|
2025-08-31 12:26:43 -05:00
|
|
|
self.assertIn("person", form)
|
|
|
|
|
self.assertNotIn("first_name", form)
|
|
|
|
|
self.assertNotIn("last_name", form)
|
|
|
|
|
with patch.object(view, "creating", new=True):
|
2025-01-27 17:07:42 -06:00
|
|
|
form = view.make_form(model_instance=barney)
|
2025-08-31 12:26:43 -05:00
|
|
|
self.assertIn("person", form)
|
|
|
|
|
self.assertNotIn("first_name", form)
|
|
|
|
|
self.assertNotIn("last_name", form)
|
2025-01-27 17:07:42 -06:00
|
|
|
view.configure_form(form)
|
2025-08-31 12:26:43 -05:00
|
|
|
self.assertNotIn("person", form)
|
|
|
|
|
self.assertIn("first_name", form)
|
|
|
|
|
self.assertIn("last_name", form)
|
|
|
|
|
with patch.object(view, "editing", new=True):
|
2025-01-27 17:07:42 -06:00
|
|
|
form = view.make_form(model_instance=barney)
|
2025-08-31 12:26:43 -05:00
|
|
|
self.assertIn("person", form)
|
|
|
|
|
self.assertNotIn("first_name", form)
|
|
|
|
|
self.assertNotIn("last_name", form)
|
2024-08-13 21:43:56 -05:00
|
|
|
view.configure_form(form)
|
2025-08-31 12:26:43 -05:00
|
|
|
self.assertNotIn("person", form)
|
|
|
|
|
self.assertIn("first_name", form)
|
|
|
|
|
self.assertIn("last_name", form)
|
2025-01-27 17:07:42 -06:00
|
|
|
|
|
|
|
|
# first/last name have default values when editing
|
2025-08-31 12:26:43 -05:00
|
|
|
with patch.object(view, "editing", new=True):
|
2025-01-27 17:07:42 -06:00
|
|
|
form = view.make_form(model_instance=barney)
|
2025-08-31 12:26:43 -05:00
|
|
|
self.assertNotIn("first_name", form.defaults)
|
|
|
|
|
self.assertNotIn("last_name", form.defaults)
|
2025-01-27 17:07:42 -06:00
|
|
|
view.configure_form(form)
|
2025-08-31 12:26:43 -05:00
|
|
|
self.assertIn("first_name", form.defaults)
|
|
|
|
|
self.assertEqual(form.defaults["first_name"], "Barney")
|
|
|
|
|
self.assertIn("last_name", form.defaults)
|
|
|
|
|
self.assertEqual(form.defaults["last_name"], "Rubble")
|
2024-08-13 21:43:56 -05:00
|
|
|
|
|
|
|
|
# password removed (always, for now)
|
2025-08-31 12:26:43 -05:00
|
|
|
with patch.object(view, "viewing", new=True):
|
2024-08-13 21:43:56 -05:00
|
|
|
form = view.make_form(model_instance=barney)
|
2025-08-31 12:26:43 -05:00
|
|
|
self.assertIn("password", form)
|
2024-08-13 21:43:56 -05:00
|
|
|
view.configure_form(form)
|
2025-08-31 12:26:43 -05:00
|
|
|
self.assertNotIn("password", form)
|
|
|
|
|
with patch.object(view, "editing", new=True):
|
2024-08-13 21:43:56 -05:00
|
|
|
form = view.make_form(model_instance=barney)
|
2025-08-31 12:26:43 -05:00
|
|
|
self.assertIn("password", form)
|
2024-08-13 21:43:56 -05:00
|
|
|
view.configure_form(form)
|
2025-08-31 12:26:43 -05:00
|
|
|
self.assertNotIn("password", form)
|
2024-08-12 21:17:08 -05:00
|
|
|
|
2025-08-09 09:56:00 -05:00
|
|
|
# api tokens grid shown only if current user has perm
|
2025-08-31 12:26:43 -05:00
|
|
|
with patch.object(view, "viewing", new=True):
|
2025-08-09 09:56:00 -05:00
|
|
|
form = view.make_form(model_instance=barney)
|
2025-08-31 12:26:43 -05:00
|
|
|
self.assertIn("api_tokens", form)
|
2025-08-09 09:56:00 -05:00
|
|
|
view.configure_form(form)
|
2025-08-31 12:26:43 -05:00
|
|
|
self.assertNotIn("api_tokens", form)
|
|
|
|
|
with patch.object(self.request, "is_root", new=True):
|
2025-08-09 09:56:00 -05:00
|
|
|
form = view.make_form(model_instance=barney)
|
2025-08-31 12:26:43 -05:00
|
|
|
self.assertIn("api_tokens", form)
|
2025-08-09 09:56:00 -05:00
|
|
|
view.configure_form(form)
|
2025-08-31 12:26:43 -05:00
|
|
|
self.assertIn("api_tokens", form)
|
2025-08-09 09:56:00 -05:00
|
|
|
|
2024-08-12 21:17:08 -05:00
|
|
|
def test_unique_username(self):
|
|
|
|
|
model = self.app.model
|
|
|
|
|
view = self.make_view()
|
|
|
|
|
|
2025-08-31 12:26:43 -05:00
|
|
|
user = model.User(username="foo")
|
2024-08-12 21:17:08 -05:00
|
|
|
self.session.add(user)
|
|
|
|
|
self.session.commit()
|
|
|
|
|
|
2025-08-31 12:26:43 -05:00
|
|
|
with patch.object(view, "Session", return_value=self.session):
|
2024-08-12 21:17:08 -05:00
|
|
|
|
|
|
|
|
# invalid if same username in data
|
2025-08-31 12:26:43 -05:00
|
|
|
node = colander.SchemaNode(colander.String(), name="username")
|
|
|
|
|
self.assertRaises(colander.Invalid, view.unique_username, node, "foo")
|
2024-08-12 21:17:08 -05:00
|
|
|
|
|
|
|
|
# but not if username belongs to current user
|
|
|
|
|
view.editing = True
|
2025-08-31 12:26:43 -05:00
|
|
|
self.request.matchdict = {"uuid": user.uuid}
|
|
|
|
|
node = colander.SchemaNode(colander.String(), name="username")
|
|
|
|
|
self.assertIsNone(view.unique_username(node, "foo"))
|
2024-08-13 21:43:56 -05:00
|
|
|
|
|
|
|
|
def test_objectify(self):
|
|
|
|
|
model = self.app.model
|
2024-08-14 15:55:10 -05:00
|
|
|
auth = self.app.get_auth_handler()
|
2025-01-27 17:07:42 -06:00
|
|
|
view = self.make_view()
|
|
|
|
|
|
2024-08-13 21:43:56 -05:00
|
|
|
blokes = model.Role(name="Blokes")
|
|
|
|
|
self.session.add(blokes)
|
|
|
|
|
others = model.Role(name="Others")
|
|
|
|
|
self.session.add(others)
|
2025-08-31 12:26:43 -05:00
|
|
|
barney = model.User(username="barney")
|
|
|
|
|
auth.set_user_password(barney, "testpass")
|
2024-08-13 21:43:56 -05:00
|
|
|
barney.roles.append(blokes)
|
|
|
|
|
self.session.add(barney)
|
|
|
|
|
self.session.commit()
|
|
|
|
|
|
2025-08-31 12:26:43 -05:00
|
|
|
with patch.object(self.request, "matchdict", new={"uuid": barney.uuid}):
|
|
|
|
|
with patch.object(view, "editing", new=True):
|
2025-01-27 17:07:42 -06:00
|
|
|
|
|
|
|
|
# sanity check, user is just in 'blokes' role
|
|
|
|
|
self.session.refresh(barney)
|
|
|
|
|
self.assertEqual(len(barney.roles), 1)
|
|
|
|
|
self.assertEqual(barney.roles[0].name, "Blokes")
|
|
|
|
|
|
|
|
|
|
# form can update user password
|
2025-08-31 12:26:43 -05:00
|
|
|
self.assertTrue(auth.check_user_password(barney, "testpass"))
|
2025-01-27 17:07:42 -06:00
|
|
|
form = view.make_model_form(model_instance=barney)
|
2025-08-31 12:26:43 -05:00
|
|
|
form.validated = {"username": "barney", "set_password": "testpass2"}
|
|
|
|
|
with patch.object(view, "Session", return_value=self.session):
|
2025-01-27 17:07:42 -06:00
|
|
|
user = view.objectify(form)
|
|
|
|
|
self.assertIs(user, barney)
|
2025-08-31 12:26:43 -05:00
|
|
|
self.assertTrue(auth.check_user_password(barney, "testpass2"))
|
2025-01-27 17:07:42 -06:00
|
|
|
|
|
|
|
|
# form can update user roles
|
|
|
|
|
form = view.make_model_form(model_instance=barney)
|
2025-08-31 12:26:43 -05:00
|
|
|
form.validated = {"username": "barney", "roles": {others.uuid}}
|
|
|
|
|
with patch.object(view, "Session", return_value=self.session):
|
2025-01-27 17:07:42 -06:00
|
|
|
user = view.objectify(form)
|
|
|
|
|
self.assertIs(user, barney)
|
|
|
|
|
self.assertEqual(len(user.roles), 1)
|
|
|
|
|
self.assertEqual(user.roles[0].name, "Others")
|
|
|
|
|
|
|
|
|
|
# person is auto-created
|
|
|
|
|
self.assertIsNone(barney.person)
|
|
|
|
|
form = view.make_model_form(model_instance=barney)
|
2025-08-31 12:26:43 -05:00
|
|
|
form.validated = {
|
|
|
|
|
"username": "barney",
|
|
|
|
|
"first_name": "Barney",
|
|
|
|
|
"last_name": "Rubble",
|
|
|
|
|
}
|
|
|
|
|
with patch.object(view, "Session", return_value=self.session):
|
2025-01-27 17:07:42 -06:00
|
|
|
user = view.objectify(form)
|
|
|
|
|
self.assertIsNotNone(barney.person)
|
2025-08-31 12:26:43 -05:00
|
|
|
self.assertEqual(barney.person.first_name, "Barney")
|
|
|
|
|
self.assertEqual(barney.person.last_name, "Rubble")
|
|
|
|
|
self.assertEqual(barney.person.full_name, "Barney Rubble")
|
2025-01-27 17:07:42 -06:00
|
|
|
|
|
|
|
|
# person is auto-removed
|
|
|
|
|
self.assertIsNotNone(barney.person)
|
|
|
|
|
form = view.make_model_form(model_instance=barney)
|
2025-08-31 12:26:43 -05:00
|
|
|
form.validated = {
|
|
|
|
|
"username": "barney",
|
|
|
|
|
"first_name": "",
|
|
|
|
|
"last_name": "",
|
|
|
|
|
}
|
|
|
|
|
with patch.object(view, "Session", return_value=self.session):
|
2025-01-27 17:07:42 -06:00
|
|
|
user = view.objectify(form)
|
|
|
|
|
self.assertIsNone(barney.person)
|
|
|
|
|
|
|
|
|
|
# nb. re-attach the person
|
|
|
|
|
barney.person = self.session.query(model.Person).one()
|
|
|
|
|
|
|
|
|
|
# person name is updated
|
2025-08-31 12:26:43 -05:00
|
|
|
self.assertEqual(barney.person.first_name, "Barney")
|
|
|
|
|
self.assertEqual(barney.person.last_name, "Rubble")
|
|
|
|
|
self.assertEqual(barney.person.full_name, "Barney Rubble")
|
2025-01-27 17:07:42 -06:00
|
|
|
form = view.make_model_form(model_instance=barney)
|
2025-08-31 12:26:43 -05:00
|
|
|
form.validated = {
|
|
|
|
|
"username": "barney",
|
|
|
|
|
"first_name": "Fred",
|
|
|
|
|
"last_name": "Flintstone",
|
|
|
|
|
}
|
|
|
|
|
with patch.object(view, "Session", return_value=self.session):
|
2025-01-27 17:07:42 -06:00
|
|
|
user = view.objectify(form)
|
|
|
|
|
self.assertIsNotNone(barney.person)
|
2025-08-31 12:26:43 -05:00
|
|
|
self.assertEqual(barney.person.first_name, "Fred")
|
|
|
|
|
self.assertEqual(barney.person.last_name, "Flintstone")
|
|
|
|
|
self.assertEqual(barney.person.full_name, "Fred Flintstone")
|
2024-08-13 21:43:56 -05:00
|
|
|
|
2025-08-31 12:26:43 -05:00
|
|
|
with patch.object(view, "creating", new=True):
|
2024-08-14 15:55:10 -05:00
|
|
|
|
2025-01-27 17:07:42 -06:00
|
|
|
# person is auto-created when making new user
|
|
|
|
|
form = view.make_model_form()
|
2025-08-31 12:26:43 -05:00
|
|
|
form.validated = {
|
|
|
|
|
"username": "betty",
|
|
|
|
|
"first_name": "Betty",
|
|
|
|
|
"last_name": "Boop",
|
|
|
|
|
}
|
|
|
|
|
with patch.object(view, "Session", return_value=self.session):
|
2025-01-27 17:07:42 -06:00
|
|
|
user = view.objectify(form)
|
|
|
|
|
self.assertIsNotNone(user.person)
|
2025-08-31 12:26:43 -05:00
|
|
|
self.assertEqual(user.person.first_name, "Betty")
|
|
|
|
|
self.assertEqual(user.person.last_name, "Boop")
|
|
|
|
|
self.assertEqual(user.person.full_name, "Betty Boop")
|
2025-01-27 17:07:42 -06:00
|
|
|
|
|
|
|
|
# nb. keep ref to last user
|
|
|
|
|
last_user = user
|
|
|
|
|
|
|
|
|
|
# person is *not* auto-created if no name provided
|
|
|
|
|
form = view.make_model_form()
|
2025-08-31 12:26:43 -05:00
|
|
|
form.validated = {"username": "betty", "first_name": "", "last_name": ""}
|
|
|
|
|
with patch.object(view, "Session", return_value=self.session):
|
2025-01-27 17:07:42 -06:00
|
|
|
user = view.objectify(form)
|
|
|
|
|
self.assertIsNone(user.person)
|
|
|
|
|
self.assertIsNot(user, last_user)
|
2024-08-13 21:43:56 -05:00
|
|
|
|
|
|
|
|
def test_update_roles(self):
|
|
|
|
|
model = self.app.model
|
|
|
|
|
auth = self.app.get_auth_handler()
|
|
|
|
|
admin = auth.get_role_administrator(self.session)
|
|
|
|
|
authed = auth.get_role_authenticated(self.session)
|
|
|
|
|
anon = auth.get_role_anonymous(self.session)
|
|
|
|
|
blokes = model.Role(name="Blokes")
|
|
|
|
|
self.session.add(blokes)
|
|
|
|
|
others = model.Role(name="Others")
|
|
|
|
|
self.session.add(others)
|
2025-08-31 12:26:43 -05:00
|
|
|
barney = model.User(username="barney")
|
2024-08-13 21:43:56 -05:00
|
|
|
barney.roles.append(blokes)
|
|
|
|
|
self.session.add(barney)
|
|
|
|
|
self.session.commit()
|
|
|
|
|
view = self.make_view()
|
|
|
|
|
view.editing = True
|
2025-08-31 12:26:43 -05:00
|
|
|
self.request.matchdict = {"uuid": barney.uuid}
|
2024-08-13 21:43:56 -05:00
|
|
|
|
|
|
|
|
# no error if data is missing roles
|
|
|
|
|
form = view.make_model_form(model_instance=barney)
|
2025-08-31 12:26:43 -05:00
|
|
|
form.validated = {"username": "barneyx"}
|
2025-01-27 17:07:42 -06:00
|
|
|
user = view.objectify(form)
|
2024-08-13 21:43:56 -05:00
|
|
|
self.assertIs(user, barney)
|
2025-08-31 12:26:43 -05:00
|
|
|
self.assertEqual(barney.username, "barneyx")
|
2024-08-13 21:43:56 -05:00
|
|
|
|
|
|
|
|
# sanity check, user is just in 'blokes' role
|
|
|
|
|
self.session.refresh(barney)
|
|
|
|
|
self.assertEqual(len(barney.roles), 1)
|
|
|
|
|
self.assertEqual(barney.roles[0].name, "Blokes")
|
|
|
|
|
|
|
|
|
|
# let's test a bunch at once to ensure:
|
|
|
|
|
# - user roles are updated
|
|
|
|
|
# - authed / anon roles are not added
|
|
|
|
|
# - admin role not added if current user is not root
|
|
|
|
|
form = view.make_model_form(model_instance=barney)
|
2025-08-31 12:26:43 -05:00
|
|
|
form.validated = {
|
|
|
|
|
"username": "barney",
|
|
|
|
|
"roles": {admin.uuid, authed.uuid, anon.uuid, others.uuid},
|
|
|
|
|
}
|
|
|
|
|
with patch.object(view, "Session", return_value=self.session):
|
2025-01-27 17:07:42 -06:00
|
|
|
user = view.objectify(form)
|
2024-08-13 21:43:56 -05:00
|
|
|
self.assertIs(user, barney)
|
|
|
|
|
self.assertEqual(len(user.roles), 1)
|
|
|
|
|
self.assertEqual(user.roles[0].name, "Others")
|
|
|
|
|
|
|
|
|
|
# let's test a bunch at once to ensure:
|
|
|
|
|
# - user roles are updated
|
|
|
|
|
# - admin role is added if current user is root
|
|
|
|
|
self.request.is_root = True
|
|
|
|
|
form = view.make_model_form(model_instance=barney)
|
2025-08-31 12:26:43 -05:00
|
|
|
form.validated = {
|
|
|
|
|
"username": "barney",
|
|
|
|
|
"roles": {admin.uuid, blokes.uuid, others.uuid},
|
|
|
|
|
}
|
|
|
|
|
with patch.object(view, "Session", return_value=self.session):
|
2025-01-27 17:07:42 -06:00
|
|
|
user = view.objectify(form)
|
2024-08-13 21:43:56 -05:00
|
|
|
self.assertIs(user, barney)
|
|
|
|
|
self.assertEqual(len(user.roles), 3)
|
|
|
|
|
role_uuids = set([role.uuid for role in user.roles])
|
|
|
|
|
self.assertEqual(role_uuids, {admin.uuid, blokes.uuid, others.uuid})
|
|
|
|
|
|
|
|
|
|
# admin role not removed if current user is not root
|
|
|
|
|
self.request.is_root = False
|
|
|
|
|
form = view.make_model_form(model_instance=barney)
|
2025-08-31 12:26:43 -05:00
|
|
|
form.validated = {"username": "barney", "roles": {blokes.uuid, others.uuid}}
|
|
|
|
|
with patch.object(view, "Session", return_value=self.session):
|
2025-01-27 17:07:42 -06:00
|
|
|
user = view.objectify(form)
|
2024-08-13 21:43:56 -05:00
|
|
|
self.assertIs(user, barney)
|
|
|
|
|
self.assertEqual(len(user.roles), 3)
|
|
|
|
|
|
|
|
|
|
# admin role is removed if current user is root
|
|
|
|
|
self.request.is_root = True
|
|
|
|
|
form = view.make_model_form(model_instance=barney)
|
2025-08-31 12:26:43 -05:00
|
|
|
form.validated = {"username": "barney", "roles": {blokes.uuid, others.uuid}}
|
|
|
|
|
with patch.object(view, "Session", return_value=self.session):
|
2025-01-27 17:07:42 -06:00
|
|
|
user = view.objectify(form)
|
2024-08-13 21:43:56 -05:00
|
|
|
self.assertIs(user, barney)
|
|
|
|
|
self.assertEqual(len(user.roles), 2)
|
2025-08-09 09:56:00 -05:00
|
|
|
|
|
|
|
|
def test_normalize_api_token(self):
|
|
|
|
|
model = self.app.model
|
|
|
|
|
auth = self.app.get_auth_handler()
|
|
|
|
|
view = self.make_view()
|
|
|
|
|
|
2025-08-31 12:26:43 -05:00
|
|
|
user = model.User(username="foo")
|
2025-08-09 09:56:00 -05:00
|
|
|
self.session.add(user)
|
2025-08-31 12:26:43 -05:00
|
|
|
token = auth.add_api_token(user, "test token")
|
2025-08-09 09:56:00 -05:00
|
|
|
self.session.commit()
|
|
|
|
|
|
|
|
|
|
normal = view.normalize_api_token(token)
|
2025-08-31 12:26:43 -05:00
|
|
|
self.assertIn("uuid", normal)
|
|
|
|
|
self.assertEqual(normal["uuid"], token.uuid.hex)
|
|
|
|
|
self.assertIn("description", normal)
|
|
|
|
|
self.assertEqual(normal["description"], "test token")
|
|
|
|
|
self.assertIn("created", normal)
|
2025-08-09 09:56:00 -05:00
|
|
|
|
|
|
|
|
def test_make_api_tokens_grid(self):
|
|
|
|
|
model = self.app.model
|
|
|
|
|
auth = self.app.get_auth_handler()
|
|
|
|
|
view = self.make_view()
|
|
|
|
|
|
2025-08-31 12:26:43 -05:00
|
|
|
user = model.User(username="foo")
|
2025-08-09 09:56:00 -05:00
|
|
|
self.session.add(user)
|
2025-08-31 12:26:43 -05:00
|
|
|
token1 = auth.add_api_token(user, "test1")
|
|
|
|
|
token2 = auth.add_api_token(user, "test2")
|
2025-08-09 09:56:00 -05:00
|
|
|
self.session.commit()
|
|
|
|
|
|
|
|
|
|
# grid should have 2 records but no tools/actions
|
|
|
|
|
grid = view.make_api_tokens_grid(user)
|
|
|
|
|
self.assertIsInstance(grid, Grid)
|
|
|
|
|
self.assertEqual(len(grid.data), 2)
|
|
|
|
|
self.assertEqual(len(grid.tools), 0)
|
|
|
|
|
self.assertEqual(len(grid.actions), 0)
|
|
|
|
|
|
|
|
|
|
# create + delete allowed
|
2025-08-31 12:26:43 -05:00
|
|
|
with patch.object(self.request, "is_root", new=True):
|
2025-08-09 09:56:00 -05:00
|
|
|
grid = view.make_api_tokens_grid(user)
|
|
|
|
|
self.assertEqual(len(grid.tools), 1)
|
2025-08-31 12:26:43 -05:00
|
|
|
self.assertIn("create", grid.tools)
|
2025-08-09 09:56:00 -05:00
|
|
|
self.assertEqual(len(grid.actions), 1)
|
2025-08-31 12:26:43 -05:00
|
|
|
self.assertEqual(grid.actions[0].key, "delete")
|
2025-08-09 09:56:00 -05:00
|
|
|
|
|
|
|
|
def test_add_api_token(self):
|
|
|
|
|
model = self.app.model
|
|
|
|
|
view = self.make_view()
|
|
|
|
|
|
2025-08-31 12:26:43 -05:00
|
|
|
user = model.User(username="foo")
|
2025-08-09 09:56:00 -05:00
|
|
|
self.session.add(user)
|
|
|
|
|
self.session.commit()
|
|
|
|
|
self.session.refresh(user)
|
|
|
|
|
self.assertEqual(len(user.api_tokens), 0)
|
|
|
|
|
|
2025-08-31 12:26:43 -05:00
|
|
|
with patch.object(view, "Session", return_value=self.session):
|
|
|
|
|
with patch.object(self.request, "matchdict", new={"uuid": user.uuid}):
|
|
|
|
|
with patch.object(
|
|
|
|
|
self.request,
|
|
|
|
|
"json_body",
|
|
|
|
|
create=True,
|
|
|
|
|
new={"description": "testing"},
|
|
|
|
|
):
|
2025-08-09 09:56:00 -05:00
|
|
|
result = view.add_api_token()
|
|
|
|
|
self.assertEqual(len(user.api_tokens), 1)
|
|
|
|
|
token = user.api_tokens[0]
|
2025-08-31 12:26:43 -05:00
|
|
|
self.assertEqual(result["token_string"], token.token_string)
|
|
|
|
|
self.assertEqual(result["description"], "testing")
|
2025-08-09 09:56:00 -05:00
|
|
|
|
|
|
|
|
def test_delete_api_token(self):
|
|
|
|
|
model = self.app.model
|
|
|
|
|
auth = self.app.get_auth_handler()
|
|
|
|
|
view = self.make_view()
|
|
|
|
|
|
2025-08-31 12:26:43 -05:00
|
|
|
user = model.User(username="foo")
|
2025-08-09 09:56:00 -05:00
|
|
|
self.session.add(user)
|
2025-08-31 12:26:43 -05:00
|
|
|
token1 = auth.add_api_token(user, "test1")
|
|
|
|
|
token2 = auth.add_api_token(user, "test2")
|
2025-08-09 09:56:00 -05:00
|
|
|
self.session.commit()
|
|
|
|
|
self.session.refresh(user)
|
|
|
|
|
self.assertEqual(len(user.api_tokens), 2)
|
|
|
|
|
|
2025-08-31 12:26:43 -05:00
|
|
|
with patch.object(view, "Session", return_value=self.session):
|
|
|
|
|
with patch.object(self.request, "matchdict", new={"uuid": user.uuid}):
|
2025-08-09 09:56:00 -05:00
|
|
|
|
|
|
|
|
# normal behavior
|
2025-08-31 12:26:43 -05:00
|
|
|
with patch.object(
|
|
|
|
|
self.request,
|
|
|
|
|
"json_body",
|
|
|
|
|
create=True,
|
|
|
|
|
new={"uuid": token1.uuid.hex},
|
|
|
|
|
):
|
2025-08-09 09:56:00 -05:00
|
|
|
result = view.delete_api_token()
|
|
|
|
|
self.assertEqual(result, {})
|
|
|
|
|
self.session.refresh(user)
|
|
|
|
|
self.assertEqual(len(user.api_tokens), 1)
|
|
|
|
|
token = user.api_tokens[0]
|
|
|
|
|
self.assertIs(token, token2)
|
|
|
|
|
|
|
|
|
|
# token for wrong user
|
2025-08-31 12:26:43 -05:00
|
|
|
user2 = model.User(username="bar")
|
2025-08-09 09:56:00 -05:00
|
|
|
self.session.add(user2)
|
2025-08-31 12:26:43 -05:00
|
|
|
token3 = auth.add_api_token(user2, "test3")
|
2025-08-09 09:56:00 -05:00
|
|
|
self.session.commit()
|
2025-08-31 12:26:43 -05:00
|
|
|
with patch.object(
|
|
|
|
|
self.request,
|
|
|
|
|
"json_body",
|
|
|
|
|
create=True,
|
|
|
|
|
new={"uuid": token3.uuid.hex},
|
|
|
|
|
):
|
2025-08-09 09:56:00 -05:00
|
|
|
result = view.delete_api_token()
|
2025-08-31 12:26:43 -05:00
|
|
|
self.assertEqual(result, {"error": "API token not found"})
|
2025-08-09 09:56:00 -05:00
|
|
|
|
|
|
|
|
# token not found
|
2025-08-31 12:26:43 -05:00
|
|
|
with patch.object(
|
|
|
|
|
self.request,
|
|
|
|
|
"json_body",
|
|
|
|
|
create=True,
|
|
|
|
|
new={"uuid": self.app.make_true_uuid().hex},
|
|
|
|
|
):
|
2025-08-09 09:56:00 -05:00
|
|
|
result = view.delete_api_token()
|
2025-08-31 12:26:43 -05:00
|
|
|
self.assertEqual(result, {"error": "API token not found"})
|
2025-12-28 22:48:36 -06:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# TODO: this test seems to work fine on its own, but not in conjunction
|
|
|
|
|
# with the next class below. will have to sort this out before adding
|
|
|
|
|
# anymore functional tests probably. but it can wait for the moment.
|
|
|
|
|
# class TestListUsers(FunctionalTestCase):
|
|
|
|
|
|
|
|
|
|
# def setUp(self):
|
|
|
|
|
# super().setUp()
|
|
|
|
|
# model = self.app.model
|
|
|
|
|
# auth = self.app.get_auth_handler()
|
|
|
|
|
|
|
|
|
|
# # add 'fred' user
|
|
|
|
|
# self.fred = model.User(username="fred")
|
|
|
|
|
# auth.set_user_password(self.fred, "fredpass")
|
|
|
|
|
# self.session.add(self.fred)
|
|
|
|
|
|
|
|
|
|
# # add 'managers' role
|
|
|
|
|
# self.managers = model.Role(name="Managers")
|
|
|
|
|
# self.fred.roles.append(self.managers)
|
|
|
|
|
# self.session.add(self.managers)
|
|
|
|
|
|
|
|
|
|
# self.session.commit()
|
|
|
|
|
|
|
|
|
|
# def test_index(self):
|
|
|
|
|
# model = self.app.model
|
|
|
|
|
# auth = self.app.get_auth_handler()
|
|
|
|
|
# testapp = self.make_webtest()
|
|
|
|
|
# csrf = self.get_csrf_token(testapp)
|
|
|
|
|
|
|
|
|
|
# # cannot list users if not logged in
|
|
|
|
|
# res = testapp.get("/users/")
|
|
|
|
|
# self.assertEqual(res.status_code, 200)
|
|
|
|
|
# self.assertIn("Access Denied", res.text)
|
|
|
|
|
# self.assertIn("Login", res.text)
|
|
|
|
|
# self.assertNotIn("fred", res.text)
|
|
|
|
|
|
|
|
|
|
# # so we login
|
|
|
|
|
# res = testapp.post(
|
|
|
|
|
# "/login",
|
|
|
|
|
# params={
|
|
|
|
|
# "_csrf": csrf,
|
|
|
|
|
# "username": "fred",
|
|
|
|
|
# "password": "fredpass",
|
|
|
|
|
# },
|
|
|
|
|
# )
|
|
|
|
|
# self.assertEqual(res.status_code, 302)
|
|
|
|
|
# self.assertEqual(res.location, "http://localhost/")
|
|
|
|
|
# res = res.follow()
|
|
|
|
|
# self.assertEqual(res.status_code, 200)
|
|
|
|
|
# self.assertNotIn("Login", res.text)
|
|
|
|
|
# self.assertIn("fred", res.text)
|
|
|
|
|
|
|
|
|
|
# perms = self.session.query(model.Permission).all()
|
|
|
|
|
# self.assertEqual(len(perms), 0)
|
|
|
|
|
# self.assertFalse(auth.has_permission(self.session, self.fred, "users.list"))
|
|
|
|
|
|
|
|
|
|
# # but we still cannot list users, b/c no perm
|
|
|
|
|
# res = testapp.get("/users/")
|
|
|
|
|
# self.assertEqual(res.status_code, 200)
|
|
|
|
|
# self.assertIn("Access Denied", res.text)
|
|
|
|
|
# self.assertNotIn("Login", res.text)
|
|
|
|
|
# self.assertIn("fred", res.text)
|
|
|
|
|
|
|
|
|
|
# # so we grant the perm
|
|
|
|
|
# auth.grant_permission(self.managers, "users.list")
|
|
|
|
|
# self.session.commit()
|
|
|
|
|
|
|
|
|
|
# perms = self.session.query(model.Permission).all()
|
|
|
|
|
|
|
|
|
|
# # now we can list users
|
|
|
|
|
# res = testapp.get("/users/")
|
|
|
|
|
# self.assertEqual(res.status_code, 200)
|
|
|
|
|
# self.assertNotIn("Access Denied", res.text)
|
|
|
|
|
# self.assertNotIn("Login", res.text)
|
|
|
|
|
# self.assertIn("fred", res.text)
|
|
|
|
|
|
|
|
|
|
# testapp.get("/logout")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestCreateUser(FunctionalTestCase):
|
|
|
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
|
super().setUp()
|
|
|
|
|
model = self.app.model
|
|
|
|
|
auth = self.app.get_auth_handler()
|
|
|
|
|
|
|
|
|
|
# add 'fred' user
|
|
|
|
|
self.fred = model.User(username="fred")
|
|
|
|
|
auth.set_user_password(self.fred, "fredpass")
|
|
|
|
|
self.session.add(self.fred)
|
|
|
|
|
|
|
|
|
|
# add 'managers' role
|
|
|
|
|
self.managers = model.Role(name="Managers")
|
|
|
|
|
self.fred.roles.append(self.managers)
|
|
|
|
|
self.session.add(self.managers)
|
|
|
|
|
|
|
|
|
|
self.session.commit()
|
|
|
|
|
|
|
|
|
|
def test_create(self):
|
|
|
|
|
model = self.app.model
|
|
|
|
|
auth = self.app.get_auth_handler()
|
|
|
|
|
testapp = self.make_webtest()
|
|
|
|
|
csrf = self.get_csrf_token(testapp)
|
|
|
|
|
|
|
|
|
|
# cannot create user if not logged in
|
|
|
|
|
res = testapp.get("/users/new")
|
|
|
|
|
self.assertEqual(res.status_code, 200)
|
|
|
|
|
self.assertIn("Access Denied", res.text)
|
|
|
|
|
self.assertIn("Login", res.text)
|
|
|
|
|
self.assertNotIn("fred", res.text)
|
|
|
|
|
|
|
|
|
|
# so we login
|
|
|
|
|
res = testapp.post(
|
|
|
|
|
"/login",
|
|
|
|
|
params={
|
|
|
|
|
"_csrf": csrf,
|
|
|
|
|
"username": "fred",
|
|
|
|
|
"password": "fredpass",
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
self.assertEqual(res.status_code, 302)
|
|
|
|
|
self.assertEqual(res.location, "http://localhost/")
|
|
|
|
|
res = res.follow()
|
|
|
|
|
self.assertEqual(res.status_code, 200)
|
|
|
|
|
self.assertNotIn("Login", res.text)
|
|
|
|
|
self.assertIn("fred", res.text)
|
|
|
|
|
|
|
|
|
|
# but we still cannot create user, b/c no perm
|
|
|
|
|
res = testapp.get("/users/new")
|
|
|
|
|
self.assertEqual(res.status_code, 200)
|
|
|
|
|
self.assertIn("Access Denied", res.text)
|
|
|
|
|
self.assertNotIn("Login", res.text)
|
|
|
|
|
self.assertIn("fred", res.text)
|
|
|
|
|
|
|
|
|
|
# so we grant the perm; then we can create user
|
|
|
|
|
auth.grant_permission(self.managers, "users.list")
|
|
|
|
|
auth.grant_permission(self.managers, "users.create")
|
|
|
|
|
auth.grant_permission(self.managers, "users.view")
|
|
|
|
|
self.session.commit()
|
|
|
|
|
|
|
|
|
|
self.assertTrue(auth.has_permission(self.session, self.fred, "users.create"))
|
|
|
|
|
|
|
|
|
|
# first get the form
|
|
|
|
|
res = testapp.get("/users/new")
|
|
|
|
|
self.assertEqual(res.status_code, 200)
|
|
|
|
|
self.assertNotIn("Access Denied", res.text)
|
|
|
|
|
self.assertNotIn("Login", res.text)
|
|
|
|
|
self.assertIn("fred", res.text)
|
|
|
|
|
self.assertIn("Username", res.text)
|
|
|
|
|
|
|
|
|
|
# then post the form; user should be created
|
|
|
|
|
res = testapp.post(
|
|
|
|
|
"/users/new",
|
|
|
|
|
[
|
|
|
|
|
("_csrf", csrf),
|
|
|
|
|
("username", "barney"),
|
|
|
|
|
("__start__", "set_password:mapping"),
|
|
|
|
|
("set_password", "barneypass"),
|
|
|
|
|
("set_password-confirm", "barneypass"),
|
|
|
|
|
("__end__", "set_password:mapping"),
|
|
|
|
|
("first_name", "Barney"),
|
|
|
|
|
("last_name", "Rubble"),
|
|
|
|
|
("__start__", "roles:sequence"),
|
|
|
|
|
("checkbox", str(self.managers.uuid)),
|
|
|
|
|
("__end__", "roles:sequence"),
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
barney = self.session.query(model.User).filter_by(username="barney").first()
|
|
|
|
|
self.assertTrue(barney)
|
|
|
|
|
self.assertEqual(res.status_code, 302)
|
|
|
|
|
self.assertEqual(res.location, f"http://localhost/users/{barney.uuid}")
|
|
|
|
|
res = res.follow()
|
|
|
|
|
self.assertEqual(res.status_code, 200)
|
|
|
|
|
self.assertNotIn("Login", res.text)
|
|
|
|
|
self.assertIn("fred", res.text)
|
|
|
|
|
self.assertIn("Barney Rubble", res.text)
|
|
|
|
|
|
|
|
|
|
testapp.get("/logout")
|