Skip to content

Fixed issue 40 #325

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

Merged
merged 3 commits into from
Nov 25, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions oauth2_provider/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,13 @@ def revoke(self):
"""
self.delete()

@property
def scopes(self):
"""
Returns a dictionary of allowed scope names (as keys) with their descriptions (as values)
"""
return {name: desc for name, desc in oauth2_settings.SCOPES.items() if name in self.scope.split()}

def __str__(self):
return self.token

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{% extends "oauth2_provider/base.html" %}

{% load i18n %}
{% block content %}
<form action="" method="post">{% csrf_token %}
<p>{% trans "Are you sure you want to delete this token?" %}</p>
<input type="submit" value="{% trans "Delete" %}" />
</form>
{% endblock %}
24 changes: 24 additions & 0 deletions oauth2_provider/templates/oauth2_provider/authorized-tokens.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{% extends "oauth2_provider/base.html" %}

{% load i18n %}
{% load url from compat %}
{% block content %}
<div class="block-center">
<h1>{% trans "Tokens" %}</h1>
<ul>
{% for authorized_token in authorized_tokens %}
<li>
{{ authorized_token.application }}
(<a href="{% url 'oauth2_provider:authorized-token-delete' authorized_token.pk %}">revoke</a>)
</li>
<ul>
{% for scope_name, scope_description in authorized_token.scopes.items %}
<li>{{ scope_name }}: {{ scope_description }}</li>
{% endfor %}
</ul>
{% empty %}
<li>{% trans "There are no authorized tokens yet." %}</li>
{% endfor %}
</ul>
</div>
{% endblock %}
30 changes: 30 additions & 0 deletions oauth2_provider/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,36 @@ def test_str(self):
app.name = "test_app"
self.assertEqual("%s" % app, "test_app")

def test_scopes_property(self):
self.client.login(username="test_user", password="123456")

app = Application.objects.create(
name="test_app",
redirect_uris="http://localhost http://example.com http://example.it",
user=self.user,
client_type=Application.CLIENT_CONFIDENTIAL,
authorization_grant_type=Application.GRANT_AUTHORIZATION_CODE,
)

access_token = AccessToken(
user=self.user,
scope='read write',
expires=0,
token='',
application=app
)

access_token2 = AccessToken(
user=self.user,
scope='write',
expires=0,
token='',
application=app
)

self.assertEqual(access_token.scopes, {'read': 'Reading scope', 'write': 'Writing scope'})
self.assertEqual(access_token2.scopes, {'write': 'Writing scope'})


@skipIf(django.VERSION < (1, 5), "Behavior is broken on 1.4 and there is no solution")
@override_settings(OAUTH2_PROVIDER_APPLICATION_MODEL='tests.TestApplication')
Expand Down
178 changes: 178 additions & 0 deletions oauth2_provider/tests/test_token_view.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
from __future__ import unicode_literals

import datetime

from django.core.urlresolvers import reverse
from django.test import TestCase
from django.utils import timezone

from ..models import get_application_model, AccessToken
from ..compat import get_user_model

Application = get_application_model()
UserModel = get_user_model()


class TestAuthorizedTokenViews(TestCase):
"""
TestCase superclass for Authorized Token Views' Test Cases
"""
def setUp(self):
self.foo_user = UserModel.objects.create_user("foo_user", "test@user.com", "123456")
self.bar_user = UserModel.objects.create_user("bar_user", "dev@user.com", "123456")

self.application = Application(
name="Test Application",
redirect_uris="http://localhost http://example.com http://example.it",
user=self.bar_user,
client_type=Application.CLIENT_CONFIDENTIAL,
authorization_grant_type=Application.GRANT_AUTHORIZATION_CODE,
)
self.application.save()

def tearDown(self):
self.foo_user.delete()
self.bar_user.delete()


class TestAuthorizedTokenListView(TestAuthorizedTokenViews):
"""
Tests for the Authorized Token ListView
"""
def test_list_view_authorization_required(self):
"""
Test that the view redirects to login page if user is not logged-in.
"""
response = self.client.get(reverse('oauth2_provider:authorized-token-list'))
self.assertEqual(response.status_code, 302)
self.assertTrue('/accounts/login/?next=' in response['Location'])

def test_empty_list_view(self):
"""
Test that when you have no tokens, an appropriate message is shown
"""
self.client.login(username="foo_user", password="123456")

response = self.client.get(reverse('oauth2_provider:authorized-token-list'))
self.assertEqual(response.status_code, 200)
self.assertIn(b'There are no authorized tokens yet.', response.content)

def test_list_view_one_token(self):
"""
Test that the view shows your token
"""
self.client.login(username="bar_user", password="123456")
AccessToken.objects.create(user=self.bar_user, token='1234567890',
application=self.application,
expires=timezone.now() + datetime.timedelta(days=1),
scope='read write')

response = self.client.get(reverse('oauth2_provider:authorized-token-list'))
self.assertEqual(response.status_code, 200)
self.assertIn(b'read', response.content)
self.assertIn(b'write', response.content)
self.assertNotIn(b'There are no authorized tokens yet.', response.content)

def test_list_view_two_tokens(self):
"""
Test that the view shows your tokens
"""
self.client.login(username="bar_user", password="123456")
AccessToken.objects.create(user=self.bar_user, token='1234567890',
application=self.application,
expires=timezone.now() + datetime.timedelta(days=1),
scope='read write')
AccessToken.objects.create(user=self.bar_user, token='0123456789',
application=self.application,
expires=timezone.now() + datetime.timedelta(days=1),
scope='read write')

response = self.client.get(reverse('oauth2_provider:authorized-token-list'))
self.assertEqual(response.status_code, 200)
self.assertNotIn(b'There are no authorized tokens yet.', response.content)

def test_list_view_shows_correct_user_token(self):
"""
Test that only currently logged-in user's tokens are shown
"""
self.client.login(username="bar_user", password="123456")
AccessToken.objects.create(user=self.foo_user, token='1234567890',
application=self.application,
expires=timezone.now() + datetime.timedelta(days=1),
scope='read write')

response = self.client.get(reverse('oauth2_provider:authorized-token-list'))
self.assertEqual(response.status_code, 200)
self.assertIn(b'There are no authorized tokens yet.', response.content)


class TestAuthorizedTokenDeleteView(TestAuthorizedTokenViews):
"""
Tests for the Authorized Token DeleteView
"""
def test_delete_view_authorization_required(self):
"""
Test that the view redirects to login page if user is not logged-in.
"""
self.token = AccessToken.objects.create(user=self.foo_user, token='1234567890',
application=self.application,
expires=timezone.now() + datetime.timedelta(days=1),
scope='read write')

response = self.client.get(reverse('oauth2_provider:authorized-token-delete', kwargs={'pk': self.token.pk}))
self.assertEqual(response.status_code, 302)
self.assertTrue('/accounts/login/?next=' in response['Location'])

def test_delete_view_works(self):
"""
Test that a GET on this view returns 200 if the token belongs to the logged-in user.
"""
self.token = AccessToken.objects.create(user=self.foo_user, token='1234567890',
application=self.application,
expires=timezone.now() + datetime.timedelta(days=1),
scope='read write')

self.client.login(username="foo_user", password="123456")
response = self.client.get(reverse('oauth2_provider:authorized-token-delete', kwargs={'pk': self.token.pk}))
self.assertEqual(response.status_code, 200)

def test_delete_view_token_belongs_to_user(self):
"""
Test that a 404 is returned when trying to GET this view with someone else's tokens.
"""
self.token = AccessToken.objects.create(user=self.foo_user, token='1234567890',
application=self.application,
expires=timezone.now() + datetime.timedelta(days=1),
scope='read write')

self.client.login(username="bar_user", password="123456")
response = self.client.get(reverse('oauth2_provider:authorized-token-delete', kwargs={'pk': self.token.pk}))
self.assertEqual(response.status_code, 404)

def test_delete_view_post_actually_deletes(self):
"""
Test that a POST on this view works if the token belongs to the logged-in user.
"""
self.token = AccessToken.objects.create(user=self.foo_user, token='1234567890',
application=self.application,
expires=timezone.now() + datetime.timedelta(days=1),
scope='read write')

self.client.login(username="foo_user", password="123456")
response = self.client.post(reverse('oauth2_provider:authorized-token-delete', kwargs={'pk': self.token.pk}))
self.assertFalse(AccessToken.objects.exists())
self.assertRedirects(response, reverse('oauth2_provider:authorized-token-list'))

def test_delete_view_only_deletes_user_own_token(self):
"""
Test that a 404 is returned when trying to POST on this view with someone else's tokens.
"""
self.token = AccessToken.objects.create(user=self.foo_user, token='1234567890',
application=self.application,
expires=timezone.now() + datetime.timedelta(days=1),
scope='read write')

self.client.login(username="bar_user", password="123456")
response = self.client.post(reverse('oauth2_provider:authorized-token-delete', kwargs={'pk': self.token.pk}))
self.assertTrue(AccessToken.objects.exists())
self.assertEqual(response.status_code, 404)
6 changes: 6 additions & 0 deletions oauth2_provider/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,9 @@
url(r'^applications/(?P<pk>\d+)/delete/$', views.ApplicationDelete.as_view(), name="delete"),
url(r'^applications/(?P<pk>\d+)/update/$', views.ApplicationUpdate.as_view(), name="update"),
)

urlpatterns += (
url(r'^authorized_tokens/$', views.AuthorizedTokensListView.as_view(), name="authorized-token-list"),
url(r'^authorized_tokens/(?P<pk>\d+)/delete/$', views.AuthorizedTokenDeleteView.as_view(),
name="authorized-token-delete"),
)
1 change: 1 addition & 0 deletions oauth2_provider/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
from .application import ApplicationRegistration, ApplicationDetail, ApplicationList, \
ApplicationDelete, ApplicationUpdate
from .generic import ProtectedResourceView, ScopedProtectedResourceView, ReadWriteScopedResourceView
from .token import AuthorizedTokensListView, AuthorizedTokenDeleteView
36 changes: 36 additions & 0 deletions oauth2_provider/views/token.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from __future__ import absolute_import, unicode_literals

from django.core.urlresolvers import reverse_lazy
from django.views.generic import ListView, DeleteView

from braces.views import LoginRequiredMixin

from ..models import AccessToken


class AuthorizedTokensListView(LoginRequiredMixin, ListView):
"""
Show a page where the current logged-in user can see his tokens so they can revoke them
"""
context_object_name = 'authorized_tokens'
template_name = 'oauth2_provider/authorized-tokens.html'
model = AccessToken

def get_queryset(self):
"""
Show only user's tokens
"""
return super(AuthorizedTokensListView, self).get_queryset()\
.select_related('application').filter(user=self.request.user)


class AuthorizedTokenDeleteView(LoginRequiredMixin, DeleteView):
"""
View for revoking a specific token
"""
template_name = 'oauth2_provider/authorized-token-delete.html'
success_url = reverse_lazy('oauth2_provider:authorized-token-list')
model = AccessToken

def get_queryset(self):
return super(AuthorizedTokenDeleteView, self).get_queryset().filter(user=self.request.user)