Commit 885785d7 by Justin Abrahms

Added a UI for confirming OAuth Access.

This will allow users to delegate permissions to a 3rd party service.
parent 2312b222
"""Pages relevant for OAuth2 confirmation."""
from common.test.acceptance.pages.lms import BASE_URL
from bok_choy.page_object import PageObject
class OAuth2Confirmation(PageObject):
"""Page for OAuth2 confirmation view."""
def __init__(self, browser, client_id="test-id", scopes=("email",)):
super(OAuth2Confirmation, self).__init__(browser)
self.client_id = client_id
self.scopes = scopes
@property
def url(self):
return "{}/oauth2/authorize?client_id={}&response_type=code&scope={}".format(
BASE_URL, self.client_id, ' '.join(self.scopes))
def is_browser_on_page(self):
return self.q(css="body.oauth2").visible
def cancel(self):
"""
Cancel the request.
This redirects to an invalid URI, because we don't want actual network
connections being made.
"""
self.q(css="input[name=cancel]").click()
def confirm(self):
"""
Confirm OAuth access
This redirects to an invalid URI, because we don't want actual network
connections being made.
"""
self.q(css="input[name=authorize]").click()
@property
def has_error(self):
"""Boolean for if the page has an error or not."""
return self.q(css=".error").present
@property
def error_message(self):
"""Text of the page's error message."""
return self.q(css='.error').text[0]
# -*- coding: utf-8 -*-
"""Tests for OAuth2 permission delegation."""
from common.test.acceptance.pages.lms.oauth2_confirmation import OAuth2Confirmation
from common.test.acceptance.pages.lms.auto_auth import AutoAuthPage
from bok_choy.web_app_test import WebAppTest
from urlparse import urlparse, parse_qsl
class OAuth2PermissionDelegationTests(WebAppTest):
"""
Tests for acceptance/denial of permission delegation requests.
"""
def setUp(self):
super(OAuth2PermissionDelegationTests, self).setUp()
self.oauth_page = OAuth2Confirmation(self.browser)
def _auth(self):
"""Authenticate the user."""
AutoAuthPage(self.browser).visit()
def _qs(self, url):
"""Parse url's querystring into a dict."""
return dict(parse_qsl(urlparse(url).query))
def test_error_for_invalid_scopes(self):
"""Requests for invalid scopes throw errors."""
self._auth()
self.oauth_page.scopes = ('email', 'does-not-exist')
assert self.oauth_page.visit()
self.assertTrue(self.oauth_page.has_error)
self.assertIn('not a valid scope', self.oauth_page.error_message)
def test_cancelling_redirects(self):
"""
If you cancel the request, you're redirected to the redirect_url with a
denied query param.
"""
self._auth()
assert self.oauth_page.visit()
self.oauth_page.cancel()
# This redirects to an invalid URI.
query = self._qs(self.browser.current_url)
self.assertEqual('access_denied', query['error'])
def test_accepting_redirects(self):
"""
If you accept the request, you're redirected to the redirect_url with
the correct query params.
"""
self._auth()
assert self.oauth_page.visit()
self.oauth_page.confirm()
# This redirects to an invalid URI.
query = self._qs(self.browser.current_url)
self.assertIn('code', query)
[
{
"fields": {
"client_id": "test-id",
"client_secret": "test-secret",
"client_type": 0,
"name": "Test OAuth2 Client",
"redirect_uri": "http://does-not-exist/",
"url": "http://does-not-exist/",
"user": null
},
"model": "oauth2.client",
"pk": 3
}
]
......@@ -105,6 +105,9 @@ for log_name, log_level in LOG_OVERRIDES:
# Enable milestones app
FEATURES['MILESTONES_APP'] = True
# Enable oauth authentication, which we test.
FEATURES['ENABLE_OAUTH2_PROVIDER'] = True
# Enable pre-requisite course
FEATURES['ENABLE_PREREQUISITE_COURSES'] = True
......
......@@ -53,6 +53,7 @@
@import 'views/shoppingcart';
@import 'views/homepage';
@import 'views/support';
@import 'views/oauth2';
@import "views/financial-assistance";
@import 'views/bookmarks';
@import 'course/auto-cert';
......
@import 'neat/neat'; // lib - Neat
.oauth2 {
@include outer-container();
.authorization-confirmation {
@include span-columns(6);
@include shift(3);
line-height: 1.5em;
padding: 50px 0;
}
}
{% extends "main_django.html" %}
{% load scope i18n %}
{% block bodyclass %}oauth2{% endblock %}
{% block body %}
<div class="authorization-confirmation">
{% if not error %}
<p>
{% blocktrans with application_name=client.name %}
<strong>{{ application_name }}</strong> would like to access your data with the following permissions:
{% endblocktrans %}
</p>
<ul>
{% for permission in oauth_data.scope|scopes %}
<li>
{% if permission == "openid" %}
{% trans "Read your user ID" %}
{% elif permission == "profile" %}
{% trans "Read your user profile" %}
{% elif permission == "email" %}
{% trans "Read your email address" %}
{% elif permission == "course_staff" %}
{% trans "Read the list of courses in which you are a staff member." %}
{% elif permission == "course_instructor" %}
{% trans "Read the list of courses in which you are an instructor." %}
{% elif permission == "permissions" %}
{% trans "To see if you are a global staff user" %}
{% else %}
{% blocktrans %}Manage your data: {{ permission }}{% endblocktrans %}
{% endif %}
</li>
{% endfor %}
</ul>
<form method="post" action="{% url "oauth2:authorize" %}">
{% csrf_token %}
{{ form.errors }}
{{ form.non_field_errors }}
<fieldset>
<div style="display: none;">
<select type="select" name="scope" multiple="multiple">
{% for scope in oauth_data.scope|scopes %}
<option value="{{ scope }}" selected="selected">{{ scope }}</option>
{% endfor %}
</select>
</div>
<input type="submit" class="btn login large danger" name="cancel" value="Cancel" />
<input type="submit" class="btn login large primary" name="authorize" value="Authorize" />
</fieldset>
</form>
{% else %}
<p class="error">
{{ error }}
{{ error_description }}
</p>
{% endif %}
</div>
{% endblock %}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment