Skip to content

Commit 926af04

Browse files
committed
Merge pull request #336 from helgi/users_hook
feat(builder hooks): app and user hooks for builder to get public keys
2 parents 802718d + 1f54a60 commit 926af04

4 files changed

Lines changed: 128 additions & 0 deletions

File tree

rootfs/api/tests/test_hooks.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,24 @@
1515

1616
from . import mock_status_ok
1717

18+
RSA_PUBKEY = (
19+
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCfQkkUUoxpvcNMkvv7jqnfodgs37M2eBO"
20+
"APgLK+KNBMaZaaKB4GF1QhTCMfFhoiTW3rqa0J75bHJcdkoobtTHlK8XUrFqsquWyg3XhsT"
21+
"Yr/3RQQXvO86e2sF7SVDJqVtpnbQGc5SgNrHCeHJmf5HTbXSIjCO/AJSvIjnituT/SIAMGe"
22+
"Bw0Nq/iSltwYAek1hiKO7wSmLcIQ8U4A00KEUtalaumf2aHOcfjgPfzlbZGP0S0cuBwSqLr"
23+
"8b5XGPmkASNdUiuJY4MJOce7bFU14B7oMAy2xacODUs1momUeYtGI9T7X2WMowJaO7tP3Gl"
24+
"sgBMP81VfYTfYChAyJpKp2yoP autotest@autotesting comment"
25+
)
26+
27+
RSA_PUBKEY2 = (
28+
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC4xELdubosJ2/bQuiSUyWclVVa71pXpmq"
29+
"aXTwfau/XFLgD5yE+TOFbVT22xvEr4AwZqS9w0TBMp4RLfi4pTdjoIK+lau2lDMuEpbF4xg"
30+
"PWAveAqKuLcKJbJrZQdo5VWn5//7+M1RHQCPqjeN2iS9I3C8yiPg3mMPT2mKuyZYB9VD3hK"
31+
"mhT4xRAsS6vfKZr7CmFHgAmRBqdaU1RetR5nfTj0R5yyAv7Z2BkE8UhUAseFZ0djBs6kzjs"
32+
"5ddgM4Gv2Zajs7qVvpVPzZpq3vFB16Q5TMj2YtoYF6UZFFf4u/4KAW8xfYJAFdpNsvh279s"
33+
"dJS08nTeElUg6pn83A3hqWX+J testing"
34+
)
35+
1836

1937
@mock.patch('api.models.release.publish_release', lambda *args: None)
2038
class HookTest(TransactionTestCase):
@@ -27,12 +45,67 @@ def setUp(self):
2745
self.user = User.objects.get(username='autotest')
2846
self.token = Token.objects.get(user=self.user).key
2947

48+
def test_key_hook(self):
49+
"""Test fetching keys for an app and a user"""
50+
51+
# Create app to use
52+
url = '/v2/apps'
53+
response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
54+
self.assertEqual(response.status_code, 201)
55+
app_id = response.data['id']
56+
57+
# give user permission to app
58+
url = "/v2/apps/{}/perms".format(app_id)
59+
body = {'username': str(self.user)}
60+
response = self.client.post(url, json.dumps(body), content_type='application/json',
61+
HTTP_AUTHORIZATION='token {}'.format(self.token))
62+
self.assertEqual(response.status_code, 201)
63+
64+
# Create key
65+
url = '/v2/keys'
66+
body = {'id': str(self.user), 'public': RSA_PUBKEY}
67+
response = self.client.post(url, json.dumps(body), content_type='application/json',
68+
HTTP_AUTHORIZATION='token {}'.format(self.token))
69+
self.assertEqual(response.status_code, 201)
70+
public = response.data['public']
71+
72+
# Create another keys
73+
url = '/v2/keys'
74+
body = {'id': str(self.user), 'public': RSA_PUBKEY2}
75+
response = self.client.post(url, json.dumps(body), content_type='application/json',
76+
HTTP_AUTHORIZATION='token {}'.format(self.token))
77+
self.assertEqual(response.status_code, 201)
78+
public2 = response.data['public']
79+
80+
# Make sure 404 is returned for a random app
81+
url = '/v2/hooks/keys/doesnotexist'
82+
response = self.client.get(url, HTTP_X_DEIS_BUILDER_AUTH=settings.BUILDER_KEY)
83+
self.assertEqual(response.status_code, 404)
84+
85+
# Test app that exists
86+
url = '/v2/hooks/keys/{}'.format(app_id)
87+
response = self.client.get(url, HTTP_X_DEIS_BUILDER_AUTH=settings.BUILDER_KEY)
88+
self.assertEqual(response.status_code, 200)
89+
self.assertEqual(response.data, {"autotest": [public, public2]})
90+
91+
# Test against an app that exist but user does not
92+
url = '/v2/hooks/keys/{}/foooooo'.format(app_id)
93+
response = self.client.get(url, HTTP_X_DEIS_BUILDER_AUTH=settings.BUILDER_KEY)
94+
self.assertEqual(response.status_code, 404)
95+
96+
# Test against an app that exists and user that does
97+
url = '/v2/hooks/keys/{}/{}'.format(app_id, str(self.user))
98+
response = self.client.get(url, HTTP_X_DEIS_BUILDER_AUTH=settings.BUILDER_KEY)
99+
self.assertEqual(response.status_code, 200)
100+
self.assertEqual(response.data, {"autotest": [public, public2]})
101+
30102
def test_push_hook(self):
31103
"""Test creating a Push via the API"""
32104
url = '/v2/apps'
33105
response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
34106
self.assertEqual(response.status_code, 201)
35107
app_id = response.data['id']
108+
36109
# prepare a push body
37110
body = {
38111
'sha': 'df1e628f2244b73f9cdf944f880a2b3470a122f4',

rootfs/api/tests/test_key.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,17 @@ def _check_key(self, pubkey):
7171
HTTP_AUTHORIZATION='token {}'.format(self.token))
7272
self.assertEqual(response.status_code, 201)
7373
key_id = response.data['id']
74+
7475
response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
7576
self.assertEqual(response.status_code, 200)
7677
self.assertEqual(len(response.data['results']), 1)
78+
7779
url = '/v2/keys/{key_id}'.format(**locals())
7880
response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
7981
self.assertEqual(response.status_code, 200)
8082
self.assertEqual(body['id'], response.data['id'])
8183
self.assertEqual(body['public'], response.data['public'])
84+
8285
response = self.client.delete(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
8386
self.assertEqual(response.status_code, 204)
8487

rootfs/api/urls.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@
7272
url(r'^keys/?',
7373
views.KeyViewSet.as_view({'get': 'list', 'post': 'create'})),
7474
# hooks
75+
url(r'^hooks/keys/(?P<id>{})/(?P<username>[-_\w]+)?'.format(settings.APP_URL_REGEX),
76+
views.KeyHookViewSet.as_view({'get': 'users'})),
77+
url(r'^hooks/keys/(?P<id>{})/?'.format(settings.APP_URL_REGEX),
78+
views.KeyHookViewSet.as_view({'get': 'app'})),
7579
url(r'^hooks/push/?',
7680
views.PushHookViewSet.as_view({'post': 'create'})),
7781
url(r'^hooks/build/?',

rootfs/api/views.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from django.core.exceptions import ValidationError
66
from django.contrib.auth.models import User
77
from django.http import HttpResponse
8+
from django.http import Http404
89
from django.shortcuts import get_object_or_404
910
from guardian.shortcuts import assign_perm, get_objects_for_user, \
1011
get_users_with_perms, remove_perm
@@ -397,6 +398,53 @@ class BaseHookViewSet(BaseDeisViewSet):
397398
permission_classes = [permissions.HasBuilderAuth]
398399

399400

401+
class KeyHookViewSet(BaseHookViewSet):
402+
"""API hook to create new :class:`~api.models.Push`"""
403+
model = models.Key
404+
serializer_class = serializers.KeySerializer
405+
406+
def app(self, request, *args, **kwargs):
407+
app = get_object_or_404(models.App, id=kwargs['id'])
408+
409+
perm_name = "api.use_app"
410+
usernames = [u.id for u in get_users_with_perms(app)
411+
if u.has_perm(perm_name, app)]
412+
413+
data = {}
414+
result = models.Key.objects \
415+
.filter(owner__in=usernames) \
416+
.values('owner__username', 'public') \
417+
.order_by('created')
418+
for info in result:
419+
user = info['owner__username']
420+
if user not in data:
421+
data[user] = []
422+
423+
data[user].append(info['public'])
424+
425+
return Response(data, status=status.HTTP_200_OK)
426+
427+
def users(self, request, *args, **kwargs):
428+
app = get_object_or_404(models.App, id=kwargs['id'])
429+
request.user = get_object_or_404(User, username=kwargs['username'])
430+
# check the user is authorized for this app
431+
if not permissions.is_app_user(request, app):
432+
raise PermissionDenied()
433+
434+
data = {request.user.username: []}
435+
keys = models.Key.objects \
436+
.filter(owner__username=kwargs['username']) \
437+
.values('public') \
438+
.order_by('created')
439+
if not keys:
440+
raise Http404("No Keys match the given query.")
441+
442+
for key in keys:
443+
data[request.user.username].append(key['public'])
444+
445+
return Response(data, status=status.HTTP_200_OK)
446+
447+
400448
class PushHookViewSet(BaseHookViewSet):
401449
"""API hook to create new :class:`~api.models.Push`"""
402450
model = models.Push

0 commit comments

Comments
 (0)