Clean up & Add support to update objects in feed API
[mediagoblin.git] / mediagoblin / oauth / views.py
... / ...
CommitLineData
1# GNU MediaGoblin -- federated, autonomous media hosting
2# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
3#
4# This program is free software: you can redistribute it and/or modify
5# it under the terms of the GNU Affero General Public License as published by
6# the Free Software Foundation, either version 3 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Affero General Public License for more details.
13#
14# You should have received a copy of the GNU Affero General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17import datetime
18import string
19
20from oauthlib.oauth1 import (RequestTokenEndpoint, AuthorizationEndpoint,
21 AccessTokenEndpoint)
22
23from mediagoblin.decorators import require_active_login
24from mediagoblin.tools.translate import pass_to_ugettext
25from mediagoblin.meddleware.csrf import csrf_exempt
26from mediagoblin.tools.request import decode_request
27from mediagoblin.tools.response import (render_to_response, redirect,
28 json_response, render_400,
29 form_response)
30from mediagoblin.tools.crypto import random_string
31from mediagoblin.tools.validator import validate_email, validate_url
32from mediagoblin.oauth.forms import AuthorizeForm
33from mediagoblin.oauth.oauth import GMGRequestValidator, GMGRequest
34from mediagoblin.oauth.tools.request import decode_authorization_header
35from mediagoblin.oauth.tools.forms import WTFormData
36from mediagoblin.db.models import NonceTimestamp, Client, RequestToken
37
38# possible client types
39CLIENT_TYPES = ["web", "native"] # currently what pump supports
40OAUTH_ALPHABET = (string.ascii_letters.decode('ascii') +
41 string.digits.decode('ascii'))
42
43@csrf_exempt
44def client_register(request):
45 """ Endpoint for client registration """
46 try:
47 data = decode_request(request)
48 except ValueError:
49 error = "Could not decode data."
50 return json_response({"error": error}, status=400)
51
52 if data is "":
53 error = "Unknown Content-Type"
54 return json_response({"error": error}, status=400)
55
56 if "type" not in data:
57 error = "No registration type provided."
58 return json_response({"error": error}, status=400)
59 if data.get("application_type", None) not in CLIENT_TYPES:
60 error = "Unknown application_type."
61 return json_response({"error": error}, status=400)
62
63 client_type = data["type"]
64
65 if client_type == "client_update":
66 # updating a client
67 if "client_id" not in data:
68 error = "client_id is requried to update."
69 return json_response({"error": error}, status=400)
70 elif "client_secret" not in data:
71 error = "client_secret is required to update."
72 return json_response({"error": error}, status=400)
73
74 client = Client.query.filter_by(
75 id=data["client_id"],
76 secret=data["client_secret"]
77 ).first()
78
79 if client is None:
80 error = "Unauthorized."
81 return json_response({"error": error}, status=403)
82
83 client.application_name = data.get(
84 "application_name",
85 client.application_name
86 )
87
88 client.application_type = data.get(
89 "application_type",
90 client.application_type
91 )
92
93 app_name = ("application_type", client.application_name)
94 if app_name in CLIENT_TYPES:
95 client.application_name = app_name
96
97 elif client_type == "client_associate":
98 # registering
99 if "client_id" in data:
100 error = "Only set client_id for update."
101 return json_response({"error": error}, status=400)
102 elif "access_token" in data:
103 error = "access_token not needed for registration."
104 return json_response({"error": error}, status=400)
105 elif "client_secret" in data:
106 error = "Only set client_secret for update."
107 return json_response({"error": error}, status=400)
108
109 # generate the client_id and client_secret
110 client_id = random_string(22, OAUTH_ALPHABET)
111 client_secret = random_string(43, OAUTH_ALPHABET)
112 expirey = 0 # for now, lets not have it expire
113 expirey_db = None if expirey == 0 else expirey
114 application_type = data["application_type"]
115
116 # save it
117 client = Client(
118 id=client_id,
119 secret=client_secret,
120 expirey=expirey_db,
121 application_type=application_type,
122 )
123
124 else:
125 error = "Invalid registration type"
126 return json_response({"error": error}, status=400)
127
128 logo_url = data.get("logo_url", client.logo_url)
129 if logo_url is not None and not validate_url(logo_url):
130 error = "Logo URL {0} is not a valid URL.".format(logo_url)
131 return json_response(
132 {"error": error},
133 status=400
134 )
135 else:
136 client.logo_url = logo_url
137
138 client.application_name = data.get("application_name", None)
139
140 contacts = data.get("contacts", None)
141 if contacts is not None:
142 if type(contacts) is not unicode:
143 error = "Contacts must be a string of space-seporated email addresses."
144 return json_response({"error": error}, status=400)
145
146 contacts = contacts.split()
147 for contact in contacts:
148 if not validate_email(contact):
149 # not a valid email
150 error = "Email {0} is not a valid email.".format(contact)
151 return json_response({"error": error}, status=400)
152
153
154 client.contacts = contacts
155
156 redirect_uris = data.get("redirect_uris", None)
157 if redirect_uris is not None:
158 if type(redirect_uris) is not unicode:
159 error = "redirect_uris must be space-seporated URLs."
160 return json_response({"error": error}, status=400)
161
162 redirect_uris = redirect_uris.split()
163
164 for uri in redirect_uris:
165 if not validate_url(uri):
166 # not a valid uri
167 error = "URI {0} is not a valid URI".format(uri)
168 return json_response({"error": error}, status=400)
169
170 client.redirect_uri = redirect_uris
171
172
173 client.save()
174
175 expirey = 0 if client.expirey is None else client.expirey
176
177 return json_response(
178 {
179 "client_id": client.id,
180 "client_secret": client.secret,
181 "expires_at": expirey,
182 })
183
184@csrf_exempt
185def request_token(request):
186 """ Returns request token """
187 try:
188 data = decode_request(request)
189 except ValueError:
190 error = "Could not decode data."
191 return json_response({"error": error}, status=400)
192
193 if data == "":
194 error = "Unknown Content-Type"
195 return json_response({"error": error}, status=400)
196
197 if not data and request.headers:
198 data = request.headers
199
200 data = dict(data) # mutableifying
201
202 authorization = decode_authorization_header(data)
203
204 if authorization == dict() or u"oauth_consumer_key" not in authorization:
205 error = "Missing required parameter."
206 return json_response({"error": error}, status=400)
207
208 # check the client_id
209 client_id = authorization[u"oauth_consumer_key"]
210 client = Client.query.filter_by(id=client_id).first()
211
212 if client == None:
213 # client_id is invalid
214 error = "Invalid client_id"
215 return json_response({"error": error}, status=400)
216
217 # make request token and return to client
218 request_validator = GMGRequestValidator(authorization)
219 rv = RequestTokenEndpoint(request_validator)
220 tokens = rv.create_request_token(request, authorization)
221
222 # store the nonce & timestamp before we return back
223 nonce = authorization[u"oauth_nonce"]
224 timestamp = authorization[u"oauth_timestamp"]
225 timestamp = datetime.datetime.fromtimestamp(float(timestamp))
226
227 nc = NonceTimestamp(nonce=nonce, timestamp=timestamp)
228 nc.save()
229
230 return form_response(tokens)
231
232@require_active_login
233def authorize(request):
234 """ Displays a page for user to authorize """
235 if request.method == "POST":
236 return authorize_finish(request)
237
238 _ = pass_to_ugettext
239 token = request.args.get("oauth_token", None)
240 if token is None:
241 # no token supplied, display a html 400 this time
242 err_msg = _("Must provide an oauth_token.")
243 return render_400(request, err_msg=err_msg)
244
245 oauth_request = RequestToken.query.filter_by(token=token).first()
246 if oauth_request is None:
247 err_msg = _("No request token found.")
248 return render_400(request, err_msg)
249
250 if oauth_request.used:
251 return authorize_finish(request)
252
253 if oauth_request.verifier is None:
254 orequest = GMGRequest(request)
255 request_validator = GMGRequestValidator()
256 auth_endpoint = AuthorizationEndpoint(request_validator)
257 verifier = auth_endpoint.create_verifier(orequest, {})
258 oauth_request.verifier = verifier["oauth_verifier"]
259
260 oauth_request.user = request.user.id
261 oauth_request.save()
262
263 # find client & build context
264 client = Client.query.filter_by(id=oauth_request.client).first()
265
266 authorize_form = AuthorizeForm(WTFormData({
267 "oauth_token": oauth_request.token,
268 "oauth_verifier": oauth_request.verifier
269 }))
270
271 context = {
272 "user": request.user,
273 "oauth_request": oauth_request,
274 "client": client,
275 "authorize_form": authorize_form,
276 }
277
278
279 # AuthorizationEndpoint
280 return render_to_response(
281 request,
282 "mediagoblin/api/authorize.html",
283 context
284 )
285
286
287def authorize_finish(request):
288 """ Finishes the authorize """
289 _ = pass_to_ugettext
290 token = request.form["oauth_token"]
291 verifier = request.form["oauth_verifier"]
292 oauth_request = RequestToken.query.filter_by(token=token, verifier=verifier)
293 oauth_request = oauth_request.first()
294
295 if oauth_request is None:
296 # invalid token or verifier
297 err_msg = _("No request token found.")
298 return render_400(request, err_msg)
299
300 oauth_request.used = True
301 oauth_request.updated = datetime.datetime.now()
302 oauth_request.save()
303
304 if oauth_request.callback == "oob":
305 # out of bounds
306 context = {"oauth_request": oauth_request}
307 return render_to_response(
308 request,
309 "mediagoblin/api/oob.html",
310 context
311 )
312
313 # okay we need to redirect them then!
314 querystring = "?oauth_token={0}&oauth_verifier={1}".format(
315 oauth_request.token,
316 oauth_request.verifier
317 )
318
319 return redirect(
320 request,
321 querystring=querystring,
322 location=oauth_request.callback
323 )
324
325@csrf_exempt
326def access_token(request):
327 """ Provides an access token based on a valid verifier and request token """
328 data = request.headers
329
330 parsed_tokens = decode_authorization_header(data)
331
332 if parsed_tokens == dict() or "oauth_token" not in parsed_tokens:
333 error = "Missing required parameter."
334 return json_response({"error": error}, status=400)
335
336
337 request.oauth_token = parsed_tokens["oauth_token"]
338 request_validator = GMGRequestValidator(data)
339 av = AccessTokenEndpoint(request_validator)
340 tokens = av.create_access_token(request, {})
341 return form_response(tokens)
342