1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
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.
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.
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/>.
19 import oauthlib
.common
20 from oauthlib
.oauth1
import (AuthorizationEndpoint
, RequestValidator
,
21 RequestTokenEndpoint
, AccessTokenEndpoint
)
23 from mediagoblin
.decorators
import require_active_login
24 from mediagoblin
.tools
.translate
import pass_to_ugettext
25 from mediagoblin
.meddleware
.csrf
import csrf_exempt
26 from mediagoblin
.tools
.request
import decode_request
, decode_authorization_header
27 from mediagoblin
.tools
.response
import (render_to_response
, redirect
,
28 json_response
, render_400
,
30 from mediagoblin
.tools
.crypto
import random_string
31 from mediagoblin
.tools
.validator
import validate_email
, validate_url
32 from mediagoblin
.db
.models
import User
, Client
, RequestToken
, AccessToken
33 from mediagoblin
.federation
.forms
import AuthorizeForm
35 # possible client types
36 client_types
= ["web", "native"] # currently what pump supports
39 def client_register(request
):
40 """ Endpoint for client registration """
42 data
= decode_request(request
)
44 error
= "Could not decode data."
45 return json_response({"error": error
}, status
=400)
48 error
= "Unknown Content-Type"
49 return json_response({"error": error
}, status
=400)
51 if "type" not in data
:
52 error
= "No registration type provided."
53 return json_response({"error": error
}, status
=400)
54 if data
.get("application_type", None) not in client_types
:
55 error
= "Unknown application_type."
56 return json_response({"error": error
}, status
=400)
58 client_type
= data
["type"]
60 if client_type
== "client_update":
62 if "client_id" not in data
:
63 error
= "client_id is requried to update."
64 return json_response({"error": error
}, status
=400)
65 elif "client_secret" not in data
:
66 error
= "client_secret is required to update."
67 return json_response({"error": error
}, status
=400)
69 client
= Client
.query
.filter_by(
71 secret
=data
["client_secret"]
75 error
= "Unauthorized."
76 return json_response({"error": error
}, status
=403)
78 client
.application_name
= data
.get(
80 client
.application_name
83 client
.application_type
= data
.get(
85 client
.application_type
88 app_name
= ("application_type", client
.application_name
)
89 if app_name
in client_types
:
90 client
.application_name
= app_name
92 elif client_type
== "client_associate":
94 if "client_id" in data
:
95 error
= "Only set client_id for update."
96 return json_response({"error": error
}, status
=400)
97 elif "access_token" in data
:
98 error
= "access_token not needed for registration."
99 return json_response({"error": error
}, status
=400)
100 elif "client_secret" in data
:
101 error
= "Only set client_secret for update."
102 return json_response({"error": error
}, status
=400)
104 # generate the client_id and client_secret
105 client_id
= random_string(22) # seems to be what pump uses
106 client_secret
= random_string(43) # again, seems to be what pump uses
107 expirey
= 0 # for now, lets not have it expire
108 expirey_db
= None if expirey
== 0 else expirey
113 secret
=client_secret
,
115 application_type
=data
["application_type"],
119 error
= "Invalid registration type"
120 return json_response({"error": error
}, status
=400)
122 logo_url
= data
.get("logo_url", client
.logo_url
)
123 if logo_url
is not None and not validate_url(logo_url
):
124 error
= "Logo URL {0} is not a valid URL.".format(logo_url
)
125 return json_response(
130 client
.logo_url
= logo_url
132 client
.application_name
= data
.get("application_name", None)
134 contacts
= data
.get("contact", None)
135 if contacts
is not None:
136 if type(contacts
) is not unicode:
137 error
= "Contacts must be a string of space-seporated email addresses."
138 return json_response({"error": error
}, status
=400)
140 contacts
= contacts
.split()
141 for contact
in contacts
:
142 if not validate_email(contact
):
144 error
= "Email {0} is not a valid email.".format(contact
)
145 return json_response({"error": error
}, status
=400)
148 client
.contacts
= contacts
150 request_uri
= data
.get("request_uris", None)
151 if request_uri
is not None:
152 if type(request_uri
) is not unicode:
153 error
= "redirect_uris must be space-seporated URLs."
154 return json_respinse({"error": error
}, status
=400)
156 request_uri
= request_uri
.split()
158 for uri
in request_uri
:
159 if not validate_url(uri
):
161 error
= "URI {0} is not a valid URI".format(uri
)
162 return json_response({"error": error
}, status
=400)
164 client
.request_uri
= request_uri
169 expirey
= 0 if client
.expirey
is None else client
.expirey
171 return json_response(
173 "client_id": client
.id,
174 "client_secret": client
.secret
,
175 "expires_at": expirey
,
178 class ValidationException(Exception):
181 class GMGRequestValidator(RequestValidator
):
183 def __init__(self
, data
=None):
186 def save_request_token(self
, token
, request
):
187 """ Saves request token in db """
188 client_id
= self
.POST
[u
"oauth_consumer_key"]
190 request_token
= RequestToken(
191 token
=token
["oauth_token"],
192 secret
=token
["oauth_token_secret"],
194 request_token
.client
= client_id
195 request_token
.callback
= token
.get("oauth_callback", None)
198 def save_verifier(self
, token
, verifier
, request
):
199 """ Saves the oauth request verifier """
200 request_token
= RequestToken
.query
.filter_by(token
=token
).first()
201 request_token
.verifier
= verifier
["oauth_verifier"]
204 def save_access_token(self
, token
, request
):
205 """ Saves access token in db """
206 access_token
= AccessToken(
207 token
=token
["oauth_token"],
208 secret
=token
["oauth_token_secret"],
210 access_token
.request_token
= request
.oauth_token
211 request_token
= RequestToken
.query
.filter_by(token
=request
.oauth_token
).first()
212 access_token
.user
= request_token
.user
215 def get_realms(*args
, **kwargs
):
216 """ Currently a stub - called when making AccessTokens """
220 def request_token(request
):
221 """ Returns request token """
223 data
= decode_request(request
)
225 error
= "Could not decode data."
226 return json_response({"error": error
}, status
=400)
229 error
= "Unknown Content-Type"
230 return json_response({"error": error
}, status
=400)
232 if not data
and request
.headers
:
233 data
= request
.headers
235 data
= dict(data
) # mutableifying
237 authorization
= decode_authorization_header(data
)
240 if authorization
== dict() or u
"oauth_consumer_key" not in authorization
:
241 error
= "Missing required parameter."
242 return json_response({"error": error
}, status
=400)
244 # check the client_id
245 client_id
= authorization
[u
"oauth_consumer_key"]
246 client
= Client
.query
.filter_by(id=client_id
).first()
248 # client_id is invalid
249 error
= "Invalid client_id"
250 return json_response({"error": error
}, status
=400)
252 # make request token and return to client
253 request_validator
= GMGRequestValidator(authorization
)
254 rv
= RequestTokenEndpoint(request_validator
)
255 tokens
= rv
.create_request_token(request
, authorization
)
257 return form_response(tokens
)
259 class WTFormData(dict):
261 Provides a WTForm usable dictionary
263 def getlist(self
, key
):
265 if not isinstance(v
, (list, tuple)):
269 @require_active_login
270 def authorize(request
):
271 """ Displays a page for user to authorize """
272 if request
.method
== "POST":
273 return authorize_finish(request
)
276 token
= request
.args
.get("oauth_token", None)
278 # no token supplied, display a html 400 this time
279 err_msg
= _("Must provide an oauth_token.")
280 return render_400(request
, err_msg
=err_msg
)
282 oauth_request
= RequestToken
.query
.filter_by(token
=token
).first()
283 if oauth_request
is None:
284 err_msg
= _("No request token found.")
285 return render_400(request
, err_msg
)
287 if oauth_request
.used
:
288 return authorize_finish(request
)
290 if oauth_request
.verifier
is None:
291 orequest
= oauthlib
.common
.Request(
293 http_method
=request
.method
,
294 body
=request
.get_data(),
295 headers
=request
.headers
297 request_validator
= GMGRequestValidator()
298 auth_endpoint
= AuthorizationEndpoint(request_validator
)
299 verifier
= auth_endpoint
.create_verifier(orequest
, {})
300 oauth_request
.verifier
= verifier
["oauth_verifier"]
302 oauth_request
.user
= request
.user
.id
305 # find client & build context
306 client
= Client
.query
.filter_by(id=oauth_request
.client
).first()
308 authorize_form
= AuthorizeForm(WTFormData({
309 "oauth_token": oauth_request
.token
,
310 "oauth_verifier": oauth_request
.verifier
314 "user": request
.user
,
315 "oauth_request": oauth_request
,
317 "authorize_form": authorize_form
,
321 # AuthorizationEndpoint
322 return render_to_response(
324 "mediagoblin/api/authorize.html",
329 def authorize_finish(request
):
330 """ Finishes the authorize """
332 token
= request
.form
["oauth_token"]
333 verifier
= request
.form
["oauth_verifier"]
334 oauth_request
= RequestToken
.query
.filter_by(token
=token
, verifier
=verifier
)
335 oauth_request
= oauth_request
.first()
337 if oauth_request
is None:
338 # invalid token or verifier
339 err_msg
= _("No request token found.")
340 return render_400(request
, err_msg
)
342 oauth_request
.used
= True
343 oauth_request
.updated
= datetime
.datetime
.now()
346 if oauth_request
.callback
== "oob":
348 context
= {"oauth_request": oauth_request
}
349 return render_to_response(
351 "mediagoblin/api/oob.html",
355 # okay we need to redirect them then!
356 querystring
= "?oauth_token={0}&oauth_verifier={1}".format(
358 oauth_request
.verifier
363 querystring
=querystring
,
364 location
=oauth_request
.callback
368 def access_token(request
):
369 """ Provides an access token based on a valid verifier and request token """
370 data
= request
.headers
372 parsed_tokens
= decode_authorization_header(data
)
374 if parsed_tokens
== dict() or "oauth_token" not in parsed_tokens
:
375 error
= "Missing required parameter."
376 return json_response({"error": error
}, status
=400)
379 request
.oauth_token
= parsed_tokens
["oauth_token"]
380 request_validator
= GMGRequestValidator(data
)
381 av
= AccessTokenEndpoint(request_validator
)
382 tokens
= av
.create_access_token(request
, {})
383 return form_response(tokens
)