Merge lp:~cprov/uci-engine/webui-user-info into lp:uci-engine

Proposed by Celso Providelo
Status: Merged
Approved by: Celso Providelo
Approved revision: 804
Merged at revision: 807
Proposed branch: lp:~cprov/uci-engine/webui-user-info
Merge into: lp:uci-engine
Diff against target: 193 lines (+147/-2)
4 files modified
ticket_system/ticket/api.py (+89/-1)
ticket_system/ticket/tests/__init__.py (+2/-1)
ticket_system/ticket/tests/test_user_info.py (+55/-0)
ticket_system/ticket_system/urls.py (+1/-0)
To merge this branch: bzr merge lp:~cprov/uci-engine/webui-user-info
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Francis Ginther Approve
Evan (community) Approve
Joe Talbott (community) Approve
Review via email: mp+235890@code.launchpad.net

Commit message

Implements and exposes a new resource in the TS API with information about the user logged in (/api/v1/userinfo/).

Description of the change

Implements and exposes a new resource in the TS API with information about the user logged in (/api/v1/userinfo/).

This resource will be consumed by the WebUI and will allow users to actually login (using openid on TS) or visualise they are logged in (username/name/email/etc).

Despite of the name, this MP only involves the TS-side of this feature.

The implementation to satisfy Tastypie is a little involving because it requires a resource wrapper to allow proper schema introspection (<resource>/schema). Other existing abstract resources (status & workflow_<statuses>) have some flaws and, at some point, should be refactored to use the just-introduced model.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:802
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1462/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1462/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:803
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1463/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1463/rebuild

review: Approve (continuous-integration)
Revision history for this message
Evan (ev) wrote :

Didn't go very deep on this, so I think it's worth getting others to review as well. Some minor nits/questions, otherwise looks very good.

review: Needs Information
Revision history for this message
Celso Providelo (cprov) wrote :

On Thu, Sep 25, 2014 at 12:55 PM, Evan Dandrea
<email address hidden> wrote:
> Review: Needs Information
>
> Didn't go very deep on this, so I think it's worth getting others to review as well. Some minor nits/questions, otherwise looks very good.

Thanks, I will get additional attention to this.

> It'd be helpful to note what the permissions actually map to. Ursula mentioned in IRC it's permission to do root things (what?) and permission to grant permission.
>

Right now, while we haven't yet implemented any authorization, the
fields preserved their original contrib.auth meaningness,
https://docs.djangoproject.com/en/1.5/ref/contrib/auth/#django.contrib.auth.models.User.is_staff

I.e. staff allows /admin access but requires specific permissions to
perform any action, and superuser gives full /admin access.

I think the none will be necessary for the ci-train transition, since
all records be public for reading and writes will require
authentication (in different forms).

I intend to use user_info.superuser to link to /admin, since ci-train
admins might need it for transitional tweaks. 'staff' is not
immediately useful, but we can use this flag during the transition to
control 'review/edit' powers in the UI. Everything is pretty much open
up to now.

>> + def test_anonymous_info(self):
>> + # Anonymous requests to /userinfo/ returns an object with
>> + # 'authenticated' set as False. The other fields are bogus/empty.
>
> Shouldn't this return 401 instead? I'm not really fussed, but it does seem strange that we return 200 with a response here.

The semantics are flexible at this point, I preferred to assume the
UserInfoResource is a public resource, intrinsically safe, which
happens to be empty when the app has no information about the user,
this way the code handling it relies on a constant schema for dealing
with authenticated and anonymous access. Making it available to
anonymous requests also gives us the ability to shelve public
information about the service (status/announce messages, profile and
display flags, etc) that are also relevant for unauthenticated
requests.

That kind of flexibility is lost if the resource become private, only
available to authenticated users, specially in an application that
offer a considerable amount of features to anonymous users.

Obviously the scenario might change as the application evolve it
shouldn't be any difficult to *privatise* this resource and deal with
Unauthorized errors in the client-side.

[]
--
Celso Providelo
<email address hidden>

Revision history for this message
Joe Talbott (joetalbott) wrote :

Looks good to me.

review: Approve
Revision history for this message
Evan (ev) wrote :

Thanks for the thorough response, Celso. That all makes sense to me. +1

review: Approve
Revision history for this message
Francis Ginther (fginther) wrote :

Your explanation helped a lot. As we finish up the other parts of the authentication work, we should be documenting how we're making use of staff and admin, etc.

review: Approve
Revision history for this message
Ubuntu CI Bot (uci-bot) wrote :
Download full text (127.8 KiB)

The attempt to merge lp:~cprov/uci-engine/webui-user-info into lp:uci-engine failed. Below is the output from the failed tests.

Running cm...
Updating source dependencies...
Updating source dependencies...
Updating source dependencies...
Updating source dependencies...
uploading webui-content.tgz to swift
Updating source dependencies...
Updating source dependencies...
Updating source dependencies...
Checking juju status
Private PPAs: disabled
Preparing local branch upload...
Uploading local branch, fingerprint dcadc56fc813d78f863a31f4b376a3a6ecaf821b
Building charm: lander
Building charm: wsgi-app
Building charm: rabbitmq-worker
Building charm: webui
Building charm: key-secret-subordinate
Building charm: system-image-server
Building charm: chroot-builder
Installing keys from bzr+ssh://bazaar.launchpad.net/~ci-engineering-private/+junk/ci-airline-dev-keys/
Running juju-deployer -v -c /tmp/tmplHx3cq/deployer/branch-source-builder.yaml -c /tmp/tmplHx3cq/deployer/britney-proxy.yaml -c /tmp/tmplHx3cq/deployer/gatekeeper.yaml -c /tmp/tmplHx3cq/deployer/image-builder.yaml -c /tmp/tmplHx3cq/deployer/lander.yaml -c /tmp/tmplHx3cq/deployer/nf-stats-service.yaml -c /tmp/tmplHx3cq/deployer/ppa-creator.yaml -c /tmp/tmplHx3cq/deployer/publisher.yaml -c /tmp/tmplHx3cq/deployer/relations.yaml -c /tmp/tmplHx3cq/deployer/test-runner.yaml -c /tmp/tmplHx3cq/deployer/ticket-system.yaml -c /tmp/tmplHx3cq/deployer/validator.yaml -c /tmp/tmplHx3cq/deployer/webui.yaml ci-airline
Tests running...
ci-utils.ci_utils.tests.test_amqp.TestAMQP.testConnectFailed ... OK (0.002 secs)
ci-utils.ci_utils.tests.test_amqp.TestAMQP.testProcessQueue ... OK (0.002 secs)
ci-utils.ci_utils.tests.test_amqp.TestAMQP.testRunForever ... OK (0.102 secs)
ci-utils.ci_utils.tests.test_amqp.TestAMQP.testSent ... OK (0.002 secs)
ci-utils.ci_utils.tests.test_amqp.TestProgressTrigger.testProgress ... OK (0.003 secs)
ci-utils.ci_utils.tests.test_amqp_worker.TestAMQPWorker.testCancel ... OK (0.104 secs)
ci-utils.ci_utils.tests.test_amqp_worker.TestAMQPWorker.testNoQueue ... OK (0.003 secs)
ci-utils.ci_utils.tests.test_amqp_worker.TestAMQPWorker.testNoTicket ... OK (0.004 secs)
ci-utils.ci_utils.tests.test_amqp_worker.TestAMQPWorker.testOnMessageCalledProcessError ... OK (0.004 secs)
ci-utils.ci_utils.tests.test_amqp_worker.TestAMQPWorker.testOnMessageFail ... OK (0.003 secs)
ci-utils.ci_utils.tests.test_amqp_worker.TestAMQPWorker.testOnMessageKilled ... OK (0.003 secs)
ci-utils.ci_utils.tests.test_amqp_worker.TestAMQPWorker.testOnMessageSimple ... OK (0.003 secs)
ci-utils.ci_utils.tests.test_amqp_worker.TestAMQPWorker.testOnMessageUnexpected ... OK (0.004 secs)
ci-utils.ci_utils.tests.test_amqp_worker.TestAMQPWorker.testSaveLastRun ... OK (0.004 secs)
ci-utils.ci_utils.tests.test_amqp_worker.TestTimer.testCBRuns ... OK (0.021 secs)
ci-utils.ci_utils.tests.test_amqp_worker.TestTimer.testCanCancel ... OK (0.000 secs)
ci-utils.ci_utils.tests.test_data_store.TestDataStoreConfig.test_invalid_auth_config ... OK (0.000 secs)
ci-utils.ci_utils.tests.test_data_store.TestDataStoreConfig.test_valid_auth_config ... OK (0.000 secs)
ci-utils.ci_utils.tests.test_data_store.TestDataStoreFileName.test_get_file_name...

lp:~cprov/uci-engine/webui-user-info updated
804. By Celso Providelo

merge trunk

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:804
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1475/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1475/rebuild

review: Approve (continuous-integration)
Revision history for this message
Evan (ev) wrote :

On 25 September 2014 20:46, Ubuntu CI Bot <email address hidden> wrote:
> The attempt to merge lp:~cprov/uci-engine/webui-user-info into lp:uci-engine failed. Below is the output from the failed tests.

Do we know what caused this?

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ticket_system/ticket/api.py'
2--- ticket_system/ticket/api.py 2014-09-24 12:32:33 +0000
3+++ ticket_system/ticket/api.py 2014-09-25 23:06:03 +0000
4@@ -26,7 +26,10 @@
5 ALL,
6 ALL_WITH_RELATIONS,
7 )
8-from tastypie.exceptions import BadRequest
9+from tastypie.exceptions import (
10+ BadRequest,
11+ NotFound,
12+)
13 from tastypie.paginator import Paginator
14 from tastypie.resources import (
15 Bundle,
16@@ -471,3 +474,88 @@
17 ticket_uuid = resp['location'].split('/')[-2]
18 Ticket.objects.get(uuid=ticket_uuid).delete()
19 return True
20+
21+
22+class ResourceWrapper(object):
23+ """Minimal wrapper class for abstract (non-ORM) resources.
24+
25+ It optionally copies given 'fields' attributes (supports nested specs
26+ and callables) from a given 'obj'.
27+
28+ It allows complete model/field instrospection (tastypie-style) and
29+ isolatation between the exposed resource and the copied (mirrored)
30+ model.
31+ """
32+
33+ def __init__(self, **kwargs):
34+ self.__dict__['_info'] = {}
35+ # Dismiss if 'obj' or 'fields' are not given.
36+ obj = kwargs.get('obj')
37+ if obj is None:
38+ return
39+ fields = kwargs.get('fields', [])
40+ # Build local attributes with values mirrored from the given
41+ # object.
42+ for name, field in fields.items():
43+ if not field.attribute:
44+ continue
45+ try:
46+ value = reduce(getattr, field.attribute.split('.'), obj)
47+ except AttributeError:
48+ value = ''
49+ else:
50+ if callable(value):
51+ value = value()
52+ self.__dict__['_info'][name] = value
53+
54+ def __getattr__(self, name):
55+ return self._info.get(name, None)
56+
57+ def to_dict(self):
58+ return self._info
59+
60+
61+class UserInfoResource(Resource):
62+ """Expose users details for the current requestor."""
63+
64+ username = fields.CharField(
65+ 'username', readonly=True,
66+ help_text='Username of the authenticated user')
67+ email = fields.CharField(
68+ 'email', readonly=True,
69+ help_text='Email address of the authenticated user.')
70+ fullname = fields.CharField(
71+ 'get_full_name', readonly=True,
72+ help_text='Full name (<first> <last>) of the authenticated user.')
73+ superuser = fields.BooleanField(
74+ 'is_superuser', readonly=True,
75+ help_text='Whether the authenticated user is a superuser or not.')
76+ staff = fields.BooleanField(
77+ 'is_staff', readonly=True,
78+ help_text='Whether the authenticated user is a staff member or not.')
79+ authenticated = fields.BooleanField(
80+ 'is_authenticated', readonly=True,
81+ help_text='Whether the user is authenticated or not.')
82+
83+ class Meta():
84+ resource_name = 'userinfo'
85+ object_class = ResourceWrapper
86+ allowed_methods = ['get']
87+
88+ def get_list(self, request, **kwargs):
89+ """Re-implement 'userinfo' resource list.
90+
91+ It actually returns a single userinfo resource corresponding to the
92+ current requestor.
93+ """
94+ user_info = ResourceWrapper(obj=request.user, fields=self.fields)
95+ return self.create_response(request, user_info.to_dict())
96+
97+ def get_object_list(self, request):
98+ """Satisfy tastypie schema instrospection (/<resource>/schema)."""
99+ user_info = ResourceWrapper(obj=request.user, fields=self.fields)
100+ return [user_info]
101+
102+ def obj_get(self, bundle, **kwargs):
103+ """Render a 404 for 'userinfo' details lookups."""
104+ raise NotFound('UserInfo does not allow details lookups')
105
106=== modified file 'ticket_system/ticket/tests/__init__.py'
107--- ticket_system/ticket/tests/__init__.py 2014-04-16 18:36:03 +0000
108+++ ticket_system/ticket/tests/__init__.py 2014-09-25 23:06:03 +0000
109@@ -13,10 +13,11 @@
110 # You should have received a copy of the GNU Affero General Public License
111 # along with this program. If not, see <http://www.gnu.org/licenses/>.
112
113+from test_full_read_api import *
114 from test_models import *
115 from test_read_api import *
116-from test_full_read_api import *
117 from test_style import *
118+from test_user_info import *
119 from test_write_api import *
120
121
122
123=== added file 'ticket_system/ticket/tests/test_user_info.py'
124--- ticket_system/ticket/tests/test_user_info.py 1970-01-01 00:00:00 +0000
125+++ ticket_system/ticket/tests/test_user_info.py 2014-09-25 23:06:03 +0000
126@@ -0,0 +1,55 @@
127+# Ubuntu Continuous Integration Engine
128+# Copyright 2013, 2014 Canonical Ltd.
129+
130+# This program is free software: you can redistribute it and/or modify it
131+# under the terms of the GNU Affero General Public License version 3, as
132+# published by the Free Software Foundation.
133+
134+# This program is distributed in the hope that it will be useful, but
135+# WITHOUT ANY WARRANTY; without even the implied warranties of
136+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
137+# PURPOSE. See the GNU Affero General Public License for more details.
138+
139+# You should have received a copy of the GNU Affero General Public License
140+# along with this program. If not, see <http://www.gnu.org/licenses/>.
141+from __future__ import unicode_literals
142+
143+from ci_utils.tastypie.test import TastypieTestCase
144+from django.contrib.auth.models import User
145+
146+
147+class UserInfoTest(TastypieTestCase):
148+
149+ def setUp(self):
150+ super(UserInfoTest, self).setUp('/api/v1')
151+
152+ def test_anonymous_info(self):
153+ # Anonymous requests to /userinfo/ returns an object with
154+ # 'authenticated' set as False. The other fields are bogus/empty.
155+ user_info = self.getResource('userinfo/')
156+ self.assertEqual({
157+ 'authenticated': False,
158+ 'username': '',
159+ 'email': '',
160+ 'fullname': '',
161+ 'staff': False,
162+ 'superuser': False,
163+ }, user_info)
164+
165+ def test_authenticated_info(self):
166+ # Authenticated requests to /userinfo/ returns the user information.
167+ password = '1234'
168+ user = User.objects.create_user(
169+ username='test', first_name='First', last_name='Last',
170+ password=password, email='t@t.com')
171+ self.client.client.login(username=user.username, password=password)
172+
173+ user_info = self.getResource('userinfo/')
174+ self.assertEqual({
175+ 'authenticated': True,
176+ 'username': 'test',
177+ 'email': 't@t.com',
178+ 'fullname': 'First Last',
179+ 'staff': False,
180+ 'superuser': False,
181+ }, user_info)
182
183=== modified file 'ticket_system/ticket_system/urls.py'
184--- ticket_system/ticket_system/urls.py 2014-08-29 18:28:56 +0000
185+++ ticket_system/ticket_system/urls.py 2014-09-25 23:06:03 +0000
186@@ -48,6 +48,7 @@
187 v1_api.register(ticket_api.SubTicketWorkflowStepStatusResource())
188 v1_api.register(ticket_api.ReviewResource())
189 v1_api.register(ticket_api.TSStatus())
190+v1_api.register(ticket_api.UserInfoResource())
191
192
193 urlpatterns = patterns(

Subscribers

People subscribed via source and target branches