Commit | Line | Data |
---|---|---|
2f5926a6 | 1 | # -*- coding: utf-8 -*- |
f46e2a4d JW |
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 | ||
f46e2a4d JW |
21 | from urllib import urlencode |
22 | from uuid import uuid4 | |
23 | from datetime import datetime | |
f46e2a4d | 24 | |
88a9662b | 25 | from mediagoblin.tools.response import render_to_response, redirect |
f46e2a4d JW |
26 | from mediagoblin.decorators import require_active_login |
27 | from mediagoblin.messages import add_message, SUCCESS, ERROR | |
28 | from mediagoblin.tools.translate import pass_to_ugettext as _ | |
88a9662b JW |
29 | from mediagoblin.plugins.oauth.models import OAuthCode, OAuthToken, \ |
30 | OAuthClient, OAuthUserClient | |
31 | from mediagoblin.plugins.oauth.forms import ClientRegistrationForm, \ | |
32 | AuthorizationForm | |
33 | from mediagoblin.plugins.oauth.tools import require_client_auth | |
34 | from mediagoblin.plugins.api.tools import json_response | |
f46e2a4d JW |
35 | |
36 | _log = logging.getLogger(__name__) | |
37 | ||
38 | ||
39 | @require_active_login | |
88a9662b JW |
40 | def register_client(request): |
41 | ''' | |
42 | Register an OAuth client | |
43 | ''' | |
111a609d | 44 | form = ClientRegistrationForm(request.form) |
f46e2a4d | 45 | |
88a9662b JW |
46 | if request.method == 'POST' and form.validate(): |
47 | client = OAuthClient() | |
111a609d JW |
48 | client.name = unicode(request.form['name']) |
49 | client.description = unicode(request.form['description']) | |
50 | client.type = unicode(request.form['type']) | |
88a9662b | 51 | client.owner_id = request.user.id |
111a609d | 52 | client.redirect_uri = unicode(request.form['redirect_uri']) |
f46e2a4d | 53 | |
88a9662b JW |
54 | client.generate_identifier() |
55 | client.generate_secret() | |
56 | ||
57 | client.save() | |
58 | ||
59 | add_message(request, SUCCESS, _('The client {0} has been registered!')\ | |
60 | .format( | |
61 | client.name)) | |
62 | ||
63 | return redirect(request, 'mediagoblin.plugins.oauth.list_clients') | |
64 | ||
65 | return render_to_response( | |
66 | request, | |
67 | 'oauth/client/register.html', | |
68 | {'form': form}) | |
69 | ||
70 | ||
71 | @require_active_login | |
72 | def list_clients(request): | |
73 | clients = request.db.OAuthClient.query.filter( | |
74 | OAuthClient.owner_id == request.user.id).all() | |
75 | return render_to_response(request, 'oauth/client/list.html', | |
76 | {'clients': clients}) | |
77 | ||
78 | ||
79 | @require_active_login | |
80 | def list_connections(request): | |
81 | connections = OAuthUserClient.query.filter( | |
82 | OAuthUserClient.user == request.user).all() | |
83 | return render_to_response(request, 'oauth/client/connections.html', | |
84 | {'connections': connections}) | |
85 | ||
86 | ||
87 | @require_active_login | |
88 | def authorize_client(request): | |
111a609d | 89 | form = AuthorizationForm(request.form) |
88a9662b JW |
90 | |
91 | client = OAuthClient.query.filter(OAuthClient.id == | |
92 | form.client_id.data).first() | |
93 | ||
94 | if not client: | |
95 | _log.error('''No such client id as received from client authorization | |
96 | form.''') | |
bf3b9e78 | 97 | return BadRequest() |
88a9662b JW |
98 | |
99 | if form.validate(): | |
100 | relation = OAuthUserClient() | |
101 | relation.user_id = request.user.id | |
102 | relation.client_id = form.client_id.data | |
103 | if form.allow.data: | |
104 | relation.state = u'approved' | |
105 | elif form.deny.data: | |
106 | relation.state = u'rejected' | |
107 | else: | |
bf3b9e78 | 108 | return BadRequest |
88a9662b JW |
109 | |
110 | relation.save() | |
111 | ||
bf3b9e78 | 112 | return redirect(request, location=form.next.data) |
88a9662b JW |
113 | |
114 | return render_to_response( | |
115 | request, | |
116 | 'oauth/authorize.html', | |
117 | {'form': form, | |
118 | 'client': client}) | |
119 | ||
120 | ||
121 | @require_client_auth | |
122 | @require_active_login | |
123 | def authorize(request, client): | |
124 | # TODO: Get rid of the JSON responses in this view, it's called by the | |
125 | # user-agent, not the client. | |
126 | user_client_relation = OAuthUserClient.query.filter( | |
127 | (OAuthUserClient.user == request.user) | |
128 | & (OAuthUserClient.client == client)) | |
129 | ||
130 | if user_client_relation.filter(OAuthUserClient.state == | |
131 | u'approved').count(): | |
132 | redirect_uri = None | |
133 | ||
134 | if client.type == u'public': | |
135 | if not client.redirect_uri: | |
136 | return json_response({ | |
137 | 'status': 400, | |
138 | 'errors': | |
139 | [u'Public clients MUST have a redirect_uri pre-set']}, | |
140 | _disable_cors=True) | |
141 | ||
142 | redirect_uri = client.redirect_uri | |
143 | ||
144 | if client.type == u'confidential': | |
145 | redirect_uri = request.GET.get('redirect_uri', client.redirect_uri) | |
146 | if not redirect_uri: | |
147 | return json_response({ | |
148 | 'status': 400, | |
149 | 'errors': [u'Can not find a redirect_uri for client: {0}'\ | |
150 | .format(client.name)]}, _disable_cors=True) | |
f46e2a4d JW |
151 | |
152 | code = OAuthCode() | |
153 | code.code = unicode(uuid4()) | |
154 | code.user = request.user | |
88a9662b | 155 | code.client = client |
f46e2a4d JW |
156 | code.save() |
157 | ||
158 | redirect_uri = ''.join([ | |
88a9662b | 159 | redirect_uri, |
f46e2a4d JW |
160 | '?', |
161 | urlencode({'code': code.code})]) | |
162 | ||
163 | _log.debug('Redirecting to {0}'.format(redirect_uri)) | |
164 | ||
bf3b9e78 | 165 | return redirect(request, location=redirect_uri) |
f46e2a4d JW |
166 | else: |
167 | # Show prompt to allow client to access data | |
168 | # - on accept: send the user agent back to the redirect_uri with the | |
169 | # code parameter | |
170 | # - on deny: send the user agent back to the redirect uri with error | |
171 | # information | |
111a609d | 172 | form = AuthorizationForm(request.form) |
88a9662b JW |
173 | form.client_id.data = client.id |
174 | form.next.data = request.url | |
175 | return render_to_response( | |
176 | request, | |
177 | 'oauth/authorize.html', | |
178 | {'form': form, | |
179 | 'client': client}) | |
f46e2a4d JW |
180 | |
181 | ||
182 | def access_token(request): | |
183 | if request.GET.get('code'): | |
88a9662b JW |
184 | code = OAuthCode.query.filter(OAuthCode.code == |
185 | request.GET.get('code')).first() | |
f46e2a4d JW |
186 | |
187 | if code: | |
111a609d JW |
188 | if code.client.type == u'confidential': |
189 | client_identifier = request.GET.get('client_id') | |
190 | ||
191 | if not client_identifier: | |
192 | return json_response({ | |
193 | 'error': 'invalid_request', | |
194 | 'error_description': | |
195 | 'Missing client_id in request'}) | |
196 | ||
197 | client_secret = request.GET.get('client_secret') | |
198 | ||
199 | if not client_secret: | |
200 | return json_response({ | |
201 | 'error': 'invalid_request', | |
202 | 'error_description': | |
203 | 'Missing client_secret in request'}) | |
204 | ||
205 | if not client_secret == code.client.secret or \ | |
206 | not client_identifier == code.client.identifier: | |
207 | return json_response({ | |
208 | 'error': 'invalid_client', | |
209 | 'error_description': | |
210 | 'The client_id or client_secret does not match the' | |
211 | ' code'}) | |
212 | ||
f46e2a4d JW |
213 | token = OAuthToken() |
214 | token.token = unicode(uuid4()) | |
215 | token.user = code.user | |
88a9662b | 216 | token.client = code.client |
f46e2a4d JW |
217 | token.save() |
218 | ||
2f5926a6 SS |
219 | # expire time of token in full seconds |
220 | # timedelta.total_seconds is python >= 2.7 or we would use that | |
221 | td = token.expires - datetime.now() | |
222 | exp_in = 86400*td.days + td.seconds # just ignore µsec | |
223 | ||
f46e2a4d JW |
224 | access_token_data = { |
225 | 'access_token': token.token, | |
88a9662b | 226 | 'token_type': 'bearer', |
2f5926a6 | 227 | 'expires_in': exp_in} |
88a9662b | 228 | return json_response(access_token_data, _disable_cors=True) |
111a609d JW |
229 | else: |
230 | return json_response({ | |
231 | 'error': 'invalid_request', | |
232 | 'error_description': | |
233 | 'Invalid code'}) | |
234 | else: | |
235 | return json_response({ | |
236 | 'error': 'invalid_request', | |
237 | 'error_descriptin': | |
238 | 'Missing `code` parameter in request'}) |