Fiy python2.7'ism (#566)
[mediagoblin.git] / mediagoblin / plugins / oauth / views.py
1 # -*- coding: utf-8 -*-
2 # GNU MediaGoblin -- federated, autonomous media hosting
3 # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU Affero General Public License for more details.
14 #
15 # You should have received a copy of the GNU Affero General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 import logging
19 import json
20
21 from webob import exc, Response
22 from urllib import urlencode
23 from uuid import uuid4
24 from datetime import datetime
25
26 from mediagoblin.tools.response import render_to_response, redirect
27 from mediagoblin.decorators import require_active_login
28 from mediagoblin.messages import add_message, SUCCESS, ERROR
29 from mediagoblin.tools.translate import pass_to_ugettext as _
30 from mediagoblin.plugins.oauth.models import OAuthCode, OAuthToken, \
31 OAuthClient, OAuthUserClient
32 from mediagoblin.plugins.oauth.forms import ClientRegistrationForm, \
33 AuthorizationForm
34 from mediagoblin.plugins.oauth.tools import require_client_auth
35 from mediagoblin.plugins.api.tools import json_response
36
37 _log = logging.getLogger(__name__)
38
39
40 @require_active_login
41 def register_client(request):
42 '''
43 Register an OAuth client
44 '''
45 form = ClientRegistrationForm(request.form)
46
47 if request.method == 'POST' and form.validate():
48 client = OAuthClient()
49 client.name = unicode(request.form['name'])
50 client.description = unicode(request.form['description'])
51 client.type = unicode(request.form['type'])
52 client.owner_id = request.user.id
53 client.redirect_uri = unicode(request.form['redirect_uri'])
54
55 client.generate_identifier()
56 client.generate_secret()
57
58 client.save()
59
60 add_message(request, SUCCESS, _('The client {0} has been registered!')\
61 .format(
62 client.name))
63
64 return redirect(request, 'mediagoblin.plugins.oauth.list_clients')
65
66 return render_to_response(
67 request,
68 'oauth/client/register.html',
69 {'form': form})
70
71
72 @require_active_login
73 def list_clients(request):
74 clients = request.db.OAuthClient.query.filter(
75 OAuthClient.owner_id == request.user.id).all()
76 return render_to_response(request, 'oauth/client/list.html',
77 {'clients': clients})
78
79
80 @require_active_login
81 def list_connections(request):
82 connections = OAuthUserClient.query.filter(
83 OAuthUserClient.user == request.user).all()
84 return render_to_response(request, 'oauth/client/connections.html',
85 {'connections': connections})
86
87
88 @require_active_login
89 def authorize_client(request):
90 form = AuthorizationForm(request.form)
91
92 client = OAuthClient.query.filter(OAuthClient.id ==
93 form.client_id.data).first()
94
95 if not client:
96 _log.error('''No such client id as received from client authorization
97 form.''')
98 return exc.HTTPBadRequest()
99
100 if form.validate():
101 relation = OAuthUserClient()
102 relation.user_id = request.user.id
103 relation.client_id = form.client_id.data
104 if form.allow.data:
105 relation.state = u'approved'
106 elif form.deny.data:
107 relation.state = u'rejected'
108 else:
109 return exc.HTTPBadRequest
110
111 relation.save()
112
113 return exc.HTTPFound(location=form.next.data)
114
115 return render_to_response(
116 request,
117 'oauth/authorize.html',
118 {'form': form,
119 'client': client})
120
121
122 @require_client_auth
123 @require_active_login
124 def authorize(request, client):
125 # TODO: Get rid of the JSON responses in this view, it's called by the
126 # user-agent, not the client.
127 user_client_relation = OAuthUserClient.query.filter(
128 (OAuthUserClient.user == request.user)
129 & (OAuthUserClient.client == client))
130
131 if user_client_relation.filter(OAuthUserClient.state ==
132 u'approved').count():
133 redirect_uri = None
134
135 if client.type == u'public':
136 if not client.redirect_uri:
137 return json_response({
138 'status': 400,
139 'errors':
140 [u'Public clients MUST have a redirect_uri pre-set']},
141 _disable_cors=True)
142
143 redirect_uri = client.redirect_uri
144
145 if client.type == u'confidential':
146 redirect_uri = request.GET.get('redirect_uri', client.redirect_uri)
147 if not redirect_uri:
148 return json_response({
149 'status': 400,
150 'errors': [u'Can not find a redirect_uri for client: {0}'\
151 .format(client.name)]}, _disable_cors=True)
152
153 code = OAuthCode()
154 code.code = unicode(uuid4())
155 code.user = request.user
156 code.client = client
157 code.save()
158
159 redirect_uri = ''.join([
160 redirect_uri,
161 '?',
162 urlencode({'code': code.code})])
163
164 _log.debug('Redirecting to {0}'.format(redirect_uri))
165
166 return exc.HTTPFound(location=redirect_uri)
167 else:
168 # Show prompt to allow client to access data
169 # - on accept: send the user agent back to the redirect_uri with the
170 # code parameter
171 # - on deny: send the user agent back to the redirect uri with error
172 # information
173 form = AuthorizationForm(request.form)
174 form.client_id.data = client.id
175 form.next.data = request.url
176 return render_to_response(
177 request,
178 'oauth/authorize.html',
179 {'form': form,
180 'client': client})
181
182
183 def access_token(request):
184 if request.GET.get('code'):
185 code = OAuthCode.query.filter(OAuthCode.code ==
186 request.GET.get('code')).first()
187
188 if code:
189 if code.client.type == u'confidential':
190 client_identifier = request.GET.get('client_id')
191
192 if not client_identifier:
193 return json_response({
194 'error': 'invalid_request',
195 'error_description':
196 'Missing client_id in request'})
197
198 client_secret = request.GET.get('client_secret')
199
200 if not client_secret:
201 return json_response({
202 'error': 'invalid_request',
203 'error_description':
204 'Missing client_secret in request'})
205
206 if not client_secret == code.client.secret or \
207 not client_identifier == code.client.identifier:
208 return json_response({
209 'error': 'invalid_client',
210 'error_description':
211 'The client_id or client_secret does not match the'
212 ' code'})
213
214 token = OAuthToken()
215 token.token = unicode(uuid4())
216 token.user = code.user
217 token.client = code.client
218 token.save()
219
220 # expire time of token in full seconds
221 # timedelta.total_seconds is python >= 2.7 or we would use that
222 td = token.expires - datetime.now()
223 exp_in = 86400*td.days + td.seconds # just ignore µsec
224
225 access_token_data = {
226 'access_token': token.token,
227 'token_type': 'bearer',
228 'expires_in': exp_in}
229 return json_response(access_token_data, _disable_cors=True)
230 else:
231 return json_response({
232 'error': 'invalid_request',
233 'error_description':
234 'Invalid code'})
235 else:
236 return json_response({
237 'error': 'invalid_request',
238 'error_descriptin':
239 'Missing `code` parameter in request'})