add oauth2 access to get and put a document (#14147)

This commit is contained in:
Jean-Baptiste Jaillet 2017-05-22 18:12:45 +02:00 committed by Josue Kouka
parent bab24b48c0
commit fe873ff083
14 changed files with 726 additions and 1 deletions

0
fargo/oauth2/__init__.py Normal file
View File

28
fargo/oauth2/admin.py Normal file
View File

@ -0,0 +1,28 @@
# fargo - document box
# Copyright (C) 2016-2017 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.contrib import admin
from .models import OAuth2Client
class OAuth2ClientAdmin(admin.ModelAdmin):
fields = ('client_name', 'client_id', 'client_secret', 'redirect_uris')
list_display = ['client_name', 'client_id', 'client_secret', 'redirect_uris']
readonly_fields = ['client_id', 'client_secret']
admin.site.register(OAuth2Client, OAuth2ClientAdmin)

34
fargo/oauth2/forms.py Normal file
View File

@ -0,0 +1,34 @@
# fargo - document box
# Copyright (C) 2016-2017 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django import forms
from django.utils.translation import ugettext_lazy as _
from fargo.fargo.models import UserDocument
class UserDocModelChoiceField(forms.ModelChoiceField):
def label_from_instance(self, obj):
return obj.filename
class OAuth2AuthorizeForm(forms.Form):
document = UserDocModelChoiceField(queryset='')
def __init__(self, user, *args, **kwargs):
super(OAuth2AuthorizeForm, self).__init__(*args, **kwargs)
self.fields['document'].queryset = UserDocument.objects.filter(user=user)
self.fields['document'].label = _('Document')

View File

@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import fargo.oauth2.models
class Migration(migrations.Migration):
dependencies = [
('fargo', '0013_document_mime_type'),
]
operations = [
migrations.CreateModel(
name='OAuth2Authorize',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('access_token', models.CharField(default=fargo.oauth2.models.generate_uuid, max_length=255)),
('code', models.CharField(default=fargo.oauth2.models.generate_uuid, max_length=255)),
('creation_date', models.DateTimeField(auto_now=True)),
('user_document', models.ForeignKey(to='fargo.UserDocument')),
],
),
migrations.CreateModel(
name='OAuth2Client',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('client_secret', models.CharField(default=fargo.oauth2.models.generate_uuid, max_length=255)),
('client_id', models.CharField(default=fargo.oauth2.models.generate_uuid, max_length=255)),
('client_name', models.CharField(max_length=255)),
('redirect_uris', models.TextField(verbose_name='redirect URIs', validators=[fargo.oauth2.models.validate_https_url])),
],
),
migrations.CreateModel(
name='OAuth2TempFile',
fields=[
('hash_key', models.CharField(max_length=128, serialize=False, primary_key=True)),
('filename', models.CharField(max_length=512)),
('document', models.ForeignKey(to='fargo.Document')),
],
),
]

View File

80
fargo/oauth2/models.py Normal file
View File

@ -0,0 +1,80 @@
# fargo - document box
# Copyright (C) 2016-2017 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import uuid
from django.core.exceptions import ValidationError
from django.core.validators import URLValidator
from django.db import models
from django.utils.translation import ugettext_lazy as _
from fargo.fargo.models import Document, UserDocument
def generate_uuid():
return uuid.uuid4().hex
def validate_https_url(data):
errors = []
data = data.strip()
if not data:
return
for url in data.split():
try:
URLValidator(schemes=['http', 'https'])(url)
except ValidationError as e:
errors.append(e)
if errors:
raise ValidationError(errors)
class OAuth2Authorize(models.Model):
user_document = models.ForeignKey(UserDocument)
access_token = models.CharField(max_length=255, default=generate_uuid)
code = models.CharField(max_length=255, default=generate_uuid)
creation_date = models.DateTimeField(auto_now=True)
def __repr__(self):
return 'OAuth2Authorize for document %r' % self.user_document
class OAuth2Client(models.Model):
client_secret = models.CharField(max_length=255, default=generate_uuid)
client_id = models.CharField(max_length=255, default=generate_uuid)
client_name = models.CharField(max_length=255)
redirect_uris = models.TextField(
verbose_name=_('redirect URIs'),
validators=[validate_https_url])
def __repr__(self):
return 'OAuth2Client name: %s with id: %s' % (self.client_name, self.client_id)
def get_redirect_uris(self):
return self.redirect_uris.split()
def check_redirect_uri(self, redirect_uri):
return redirect_uri in self.redirect_uris.strip().split()
class OAuth2TempFile(models.Model):
hash_key = models.CharField(max_length=128, primary_key=True)
document = models.ForeignKey(Document)
filename = models.CharField(max_length=512)
def save(self, *args, **kwargs):
self.hash_key = self.document.content_hash
return super(OAuth2TempFile, self).save(*args, **kwargs)

30
fargo/oauth2/urls.py Normal file
View File

@ -0,0 +1,30 @@
# fargo - document box
# Copyright (C) 2016-2017 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.conf.urls import url
from django.contrib.auth.decorators import login_required
from .views import (OAuth2AuthorizeView, get_document_token, get_document,
OAuth2AuthorizePutView, put_document)
urlpatterns = [
url(r'get-document/authorize', login_required(OAuth2AuthorizeView.as_view()), name='oauth2-authorize'),
url(r'get-document/token', get_document_token, name='oauth2-get-token'),
url(r'get-document/', get_document, name='oauth2-get-document'),
url(r'put-document/$', put_document, name='oauth2-put-document'),
url(r'put-document/(?P<pk>\w+)/authorize', login_required(OAuth2AuthorizePutView.as_view()),
name='oauth2-put-document-authorize')
]

278
fargo/oauth2/views.py Normal file
View File

@ -0,0 +1,278 @@
# fargo - document box
# Copyright (C) 2016-2017 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import cgi
import base64
import urllib
from urllib import quote, unquote
from django.core.files.base import ContentFile
from django.core.urlresolvers import reverse
from django.http import (HttpResponse, HttpResponseBadRequest,
HttpResponseRedirect, JsonResponse)
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import FormView, TemplateView
from .forms import OAuth2AuthorizeForm
from .models import OAuth2Authorize, OAuth2Client, OAuth2TempFile
from fargo.fargo.models import UserDocument, Document
class OAuth2Exception(Exception):
pass
class OAuth2AuthorizeView(FormView):
template_name = 'oauth2/authorize.html'
form_class = OAuth2AuthorizeForm
success_url = '/'
def get(self, request, *args, **kwargs):
redirect_uri = request.GET.get('redirect_uri')
if not redirect_uri:
return HttpResponseBadRequest('missing parameter redirect_uri')
client_id = request.GET.get('client_id')
response_type = request.GET.get('response_type')
if not client_id or not response_type:
uri = self.error_redirect(redirect_uri, 'invalid_request')
return HttpResponseRedirect(uri)
if response_type != 'code':
uri = self.error_redirect(redirect_uri, 'unsupported_response_type')
return HttpResponseRedirect(uri)
try:
client = OAuth2Client.objects.get(client_id=client_id)
if not client.check_redirect_uri(redirect_uri):
uri = self.error_redirect(redirect_uri, 'invalid_redirect_uri')
return HttpResponseRedirect(uri)
except OAuth2Client.DoesNotExist:
uri = self.error_redirect(redirect_uri, 'unauthorized_client')
return HttpResponseRedirect(uri)
request.session['redirect_uri'] = redirect_uri
return super(OAuth2AuthorizeView, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
if 'cancel' in request.POST:
uri = self.error_redirect(request.session['redirect_uri'], 'access_denied')
return HttpResponseRedirect(uri)
else:
return super(OAuth2AuthorizeView, self).post(request, *args, **kwargs)
def get_form_kwargs(self):
kwargs = super(OAuth2AuthorizeView, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
def error_redirect(self, redirect_uri, error_code):
if not redirect_uri[-1] == '/':
redirect_uri += '/'
redirect_uri += '?'
return redirect_uri + urllib.urlencode({'error': error_code})
def form_valid(self, form):
doc = form.cleaned_data['document']
authorization = OAuth2Authorize.objects.create(user_document=doc)
getvars = {'code': authorization.code}
if 'state' in self.request.GET:
getvars['state'] = self.request.GET['state']
redirect_uri = self.request.session['redirect_uri']
if not redirect_uri[-1] == '/':
redirect_uri += '/'
self.success_url = redirect_uri + '?' + urllib.urlencode(getvars)
return super(OAuth2AuthorizeView, self).form_valid(form)
@csrf_exempt
def get_document_token(request):
grant_type = request.POST.get('grant_type')
redirect_uri = request.POST.get('redirect_uri')
code = request.POST.get('code')
client = authenticate_client(request)
try:
if not client:
raise OAuth2Exception('invalid client')
if redirect_uri not in client.get_redirect_uris():
raise OAuth2Exception('invalid_request')
if grant_type != 'authorization_code':
raise OAuth2Exception('unsupported_grant_type')
except OAuth2Exception as e:
return JsonResponse({'error': e.message}, status=400)
try:
doc_token = OAuth2Authorize.objects.get(code=code).access_token
except OAuth2Authorize.DoesNotExist:
return JsonResponse({'error': 'invalid_request'}, status=400)
return JsonResponse({'access_token': doc_token, 'expires': '3600'})
def get_document(request):
oauth_authorize = authenticate_bearer(request)
if not oauth_authorize:
return HttpResponseBadRequest('http bearer authentication failed: invalid authorization header')
doc = oauth_authorize.user_document
response = HttpResponse(content=doc.document.content, status=200,
content_type='application/octet-stream')
ascii_filename = doc.filename.encode('ascii', 'replace')
percent_encoded_filename = quote(doc.filename.encode('utf8'), safe='')
response['Content-Disposition'] = 'attachement; filename="%s"; filename*=UTF-8\'\'%s' % (ascii_filename,
percent_encoded_filename)
return response
def authenticate_bearer(request):
authorization = request.META.get('HTTP_AUTHORIZATION')
if not authorization:
return False
splitted = authorization.split()
if len(splitted) < 2:
return False
if splitted[0] != 'Bearer':
return False
token = splitted[1]
try:
return OAuth2Authorize.objects.get(access_token=token)
except OAuth2Authorize.DoesNotExist:
return False
def authenticate_client(request, client=False):
'''Authenticate client on the token endpoint'''
if 'HTTP_AUTHORIZATION' in request.META:
authorization = request.META['HTTP_AUTHORIZATION'].split()
if authorization[0] != 'Basic' or len(authorization) != 2:
return False
try:
decoded = base64.b64decode(authorization[1])
except TypeError:
return False
parts = decoded.split(':')
if len(parts) != 2:
return False
client_id, client_secret = parts
elif 'client_id' in request.POST:
client_id = request.POST['client_id']
client_secret = request.POST.get('client_secret', '')
else:
return False
if not client:
try:
client = OAuth2Client.objects.get(client_id=client_id)
except OAuth2Client.DoesNotExist:
return False
if client.client_secret != client_secret:
return False
return client
def get_content_disposition_value(request):
if 'HTTP_CONTENT_DISPOSITION' not in request.META:
return None, 'missing content-disposition header'
content_header = request.META['HTTP_CONTENT_DISPOSITION']
disposition_type, filename = cgi.parse_header(content_header)
if disposition_type != 'attachement':
return None, 'wrong disposition type: attachement excpected'
if 'filename*' in filename:
encode, country, name = filename['filename*'].split("'")
# check accepted charset from rfc 5987
if encode == 'UTF-8':
return unquote(name.decode('utf8')), None
elif encode == 'ISO-8859-1':
return unquote(name.decode('iso-8859-1')), None
else:
return None, 'unknown encoding: UTF-8 or ISO-8859-1 allowed'
elif 'filename' in filename:
return filename['filename'], None
else:
# no filename in header
return None, 'missing filename(*) parameter in header'
@csrf_exempt
def put_document(request):
client = authenticate_client(request)
if not client:
return HttpResponseBadRequest('basic HTTP authentication failed')
filename, error = get_content_disposition_value(request)
if error:
return HttpResponseBadRequest(error)
f = ContentFile(request.body, name=filename)
document = Document.objects.get_by_file(f)
oauth2_document = OAuth2TempFile.objects.create(document=document,
filename=filename)
uri = reverse('oauth2-put-document-authorize',
args=[oauth2_document.pk]) + '/'
response = HttpResponse()
response['Location'] = uri
return response
class OAuth2AuthorizePutView(TemplateView):
template_name = 'oauth2/confirm.html'
def get_context_data(self, **kwargs):
context = super(OAuth2AuthorizePutView, self).get_context_data(**kwargs)
try:
oauth2_document = OAuth2TempFile.objects.get(pk=kwargs['pk'])
except OAuth2TempFile.DoesNotExist:
context['error_message'] = 'The document has not been uploaded'
context['redirect_uri'] = self.request.GET['redirect_uri']
return context
user_document = UserDocument.objects.filter(user=self.request.user,
document=oauth2_document.document)
if user_document:
context['error_message'] = 'This document is already in your portfolio'
context['redirect_uri'] = self.request.GET['redirect_uri']
return context
else:
context['filename'] = oauth2_document.filename
context['error_message'] = ''
return context
def get(self, request, *args, **kwargs):
redirect_uri = request.GET.get('redirect_uri', '')
if not redirect_uri:
return HttpResponseBadRequest('missing redirect_uri parameter')
request.session['redirect_uri'] = redirect_uri
return super(OAuth2AuthorizePutView, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
oauth2_document = OAuth2TempFile.objects.get(pk=kwargs['pk'])
if 'cancel' in request.POST:
error = urllib.urlencode({'error': 'access_denied'})
oauth2_document.delete()
uri = request.session['redirect_uri'] + '?' + error
return HttpResponseRedirect(uri)
UserDocument.objects.create(
user=request.user, document=oauth2_document.document,
filename=oauth2_document.filename)
return HttpResponseRedirect(request.session['redirect_uri'])

View File

@ -42,6 +42,7 @@ INSTALLED_APPS = (
'gadjo',
'fargo.fargo',
'rest_framework',
'fargo.oauth2',
)
MIDDLEWARE_CLASSES = (

View File

@ -0,0 +1,12 @@
{% extends "fargo/base.html" %}
{% load render_table from django_tables2 %}
{% load i18n %}
{% block content %}
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" name="submit" value="{% trans "Choose" %}">
<input type="submit" name="cancel" value="{% trans "Cancel" %}">
</form>
{% endblock %}

View File

@ -0,0 +1,21 @@
{% extends "fargo/base.html" %}
{% load render_table from django_tables2 %}
{% load i18n %}
{% block content %}
<div id="user-files">
{% if error_message %}
<p>{% trans error_message %}</p>
<a href="{{ redirect_uri }}">{% trans "Continue to your client url" %}</a>
{% else %}
<p>{% blocktrans %}
Do you accept to add<b> {{ filename }} </b>to your portfolio ?
{% endblocktrans %}</p>
<form id="send-file" method="post" enctype="multipart/form-data" action="{% url 'oauth2-put-document-authorize' pk %}">
{% csrf_token %}
<input type="submit" name="submit" value="{% trans "Allow" %}"/>
<input type="submit" name="cancel" value="{% trans "Cancel" %}"/>
</form>
{% endif %}
</div>
{% endblock %}

View File

@ -30,8 +30,8 @@ urlpatterns = [
url(r'^api/documents/push/$', push_document, name='fargo-api-push-document'),
url(r'^api/documents/recently-added/$', recent_documents),
url(r'^api/', include(router.urls)),
url(r'^api/', include('fargo.oauth2.urls')),
]
if 'mellon' in settings.INSTALLED_APPS:
urlpatterns.append(url(r'^accounts/mellon/', include('mellon.urls')))

162
tests/test_oauth2.py Normal file
View File

@ -0,0 +1,162 @@
import pytest
from urllib import quote
import urlparse
from django.core.files.base import ContentFile
from django.core.urlresolvers import reverse
from django.utils.http import urlencode
from fargo.oauth2.models import OAuth2Client, OAuth2Authorize, OAuth2TempFile
from fargo.fargo.models import Document, UserDocument
from test_manager import login
pytestmark = pytest.mark.django_db
@pytest.fixture
def oauth2_client():
return OAuth2Client.objects.create(
client_name='test_oauth2', client_id='client-id', client_secret='client-secret',
redirect_uris='https://example.net/document https://doc.example.net/ https://example.com')
@pytest.fixture
def document():
with open('tests/test_oauth2.txt', 'rb') as f:
content = ContentFile(f.read(), 'test_oauth2.txt')
return Document.objects.get_by_file(content)
@pytest.fixture
def user_doc(document, john_doe):
return UserDocument.objects.create(user=john_doe, document=document, filename='Baudelaire.txt')
def assert_error_redirect(url, error):
assert urlparse.urlparse(url).query == 'error=%s' % error
def test_get_document_oauth2(app, john_doe, oauth2_client, user_doc):
login(app, user=john_doe)
url = reverse('oauth2-authorize')
params = {
'client_secret': oauth2_client.client_secret,
'response_type': 'code',
'state': 'achipeachope'
}
# test missing redirect_uri
resp = app.get(url, params={}, status=400)
assert resp.content == 'missing parameter redirect_uri'
# test missing client id
params['redirect_uri'] = 'https://toto.example.com'
resp = app.get(url, params=params, status=302)
assert_error_redirect(resp.url, 'invalid_request')
# test invalid response type
params['client_id'] = oauth2_client.client_id
params['response_type'] = 'token'
resp = app.get(url, params=params, status=302)
assert_error_redirect(resp.url, 'unsupported_response_type')
# test invalid redirect uri
params['response_type'] = 'code'
resp = app.get(url, params=params, status=302)
assert_error_redirect(resp.url, 'invalid_redirect_uri')
params['redirect_uri'] = 'https://example.com'
resp = app.get(url, params=params)
assert resp.status_code == 200
assert len(resp.forms[0]['document'].options) == 2
assert 'Baudelaire.txt' in resp.forms[0]['document'].options[1]
resp.forms[0]['document'].select('1')
resp = resp.forms[0].submit()
assert len(OAuth2Authorize.objects.filter(user_document__user=john_doe)) == 1
auth = OAuth2Authorize.objects.filter(user_document__user=john_doe)[0]
assert resp.status_code == 302
assert 'code' in resp.location
assert auth.code in resp.location
assert 'state' in resp.location
assert 'achipeachope' in resp.location
params.pop('response_type')
params.pop('state')
params['grant_type'] = 'authorization_code'
params['code'] = auth.code
url = reverse('oauth2-get-token')
resp = app.post(url, params=params, status=200)
assert 'access_token' in resp.json
assert 'expires' in resp.json
assert resp.json['access_token'] == auth.access_token
url = reverse('oauth2-get-document')
app.authorization = ('Bearer', str(auth.access_token))
resp = app.get(url, status=200)
assert resp.content_type == 'application/octet-stream'
assert 'Content-disposition' in resp.headers
content_disposition = resp.content_disposition.replace(' ', '').split(';')
assert content_disposition[0] == 'attachement'
assert content_disposition[1] == 'filename="Baudelaire.txt"'
assert content_disposition[2] == 'filename*=UTF-8\'\'Baudelaire.txt'
def test_put_document(app, john_doe, oauth2_client):
login(app, user=john_doe)
with open('tests/test_oauth2.txt', 'rb') as f:
data = f.read()
url = reverse('oauth2-put-document')
resp = app.post(url, params=data, status=400)
assert 'basic HTTP authentication failed' in resp.content
app.authorization = ('Basic', (str(oauth2_client.client_id), str(oauth2_client.client_secret)))
resp = app.post(url, params=data, status=400)
assert 'missing content-disposition header' in resp.content
filename = 'Baudelaire.txt'.encode('ascii', 'replace')
percent_encode_filename = quote(filename.encode('utf8'), safe='')
headers = {
'Content-disposition': 'attachement; filename="%s"; filename*=UTF-8\'\'%s' % (filename, percent_encode_filename)
}
assert len(OAuth2TempFile.objects.all()) == 0
resp = app.post(url, params=data, headers=headers, status=200)
assert len(OAuth2TempFile.objects.all()) == 1
doc = OAuth2TempFile.objects.all()[0]
location = reverse('oauth2-put-document-authorize', kwargs={'pk': doc.pk})
assert location in resp.location
app.authorization = None
url = location + '?%s' % urlencode({'redirect_uri': 'https://example.com'})
resp = app.get(url, status=200)
assert len(UserDocument.objects.all()) == 0
resp = resp.forms[0].submit()
assert resp.status_code == 302
assert resp.location == 'https://example.com'
try:
user_document = UserDocument.objects.get(user=john_doe, document=doc.document)
except UserDocument.DoesNotExist:
assert False
assert user_document.filename == 'Baudelaire.txt'
def test_confirm_put_document_file_exception(app, john_doe, user_doc):
login(app, user=john_doe)
oauth_tmp_file = OAuth2TempFile.objects.create(document=user_doc.document, filename=user_doc.filename)
url = reverse('oauth2-put-document-authorize', kwargs={'pk': 'fakemofo'})
url += '?%s' % urlencode({'redirect_uri': 'https://example.com'})
resp = app.get(url)
assert 'The document has not been uploaded' in resp.content
url = reverse('oauth2-put-document-authorize', kwargs={'pk': oauth_tmp_file.pk})
url += '?%s' % urlencode({'redirect_uri': 'https://example.com'})
resp = app.get(url)
assert 'This document is already in your portfolio' in resp.content

36
tests/test_oauth2.txt Normal file
View File

@ -0,0 +1,36 @@
Poème hymne à la beauté du recueil les fleurs du mal de Charles Baudelaire
Viens-tu du ciel profond ou sors-tu de l'abîme,
Ô Beauté ! ton regard, infernal et divin,
Verse confusément le bienfait et le crime,
Et l'on peut pour cela te comparer au vin.
Tu contiens dans ton oeil le couchant et l'aurore ;
Tu répands des parfums comme un soir orageux ;
Tes baisers sont un philtre et ta bouche une amphore
Qui font le héros lâche et l'enfant courageux.
Sors-tu du gouffre noir ou descends-tu des astres ?
Le Destin charmé suit tes jupons comme un chien ;
Tu sèmes au hasard la joie et les désastres,
Et tu gouvernes tout et ne réponds de rien.
Tu marches sur des morts, Beauté, dont tu te moques ;
De tes bijoux l'Horreur n'est pas le moins charmant,
Et le Meurtre, parmi tes plus chères breloques,
Sur ton ventre orgueilleux danse amoureusement.
L'éphémère ébloui vole vers toi, chandelle,
Crépite, flambe et dit : Bénissons ce flambeau !
L'amoureux pantelant incliné sur sa belle
A l'air d'un moribond caressant son tombeau.
Que tu viennes du ciel ou de l'enfer, qu'importe,
Ô Beauté ! monstre énorme, effrayant, ingénu !
Si ton oeil, ton souris, ton pied, m'ouvrent la porte
D'un Infini que j'aime et n'ai jamais connu ?
De Satan ou de Dieu, qu'importe ? Ange ou Sirène,
Qu'importe, si tu rends, - fée aux yeux de velours,
Rythme, parfum, lueur, ô mon unique reine ! -
L'univers moins hideux et les instants moins lourds ?