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/>.
22 from oauthlib
.oauth1
.rfc5849
.utils
import UNICODE_ASCII_CHARACTER_SET
23 from oauthlib
.oauth1
import (RequestTokenEndpoint
, AuthorizationEndpoint
,
26 from mediagoblin
.decorators
import require_active_login
27 from mediagoblin
.tools
.translate
import pass_to_ugettext
28 from mediagoblin
.meddleware
.csrf
import csrf_exempt
29 from mediagoblin
.tools
.request
import decode_request
30 from mediagoblin
.tools
.response
import (render_to_response
, redirect
,
31 json_response
, render_400
,
33 from mediagoblin
.tools
.crypto
import random_string
34 from mediagoblin
.tools
.validator
import validate_email
, validate_url
35 from mediagoblin
.oauth
.forms
import AuthorizeForm
36 from mediagoblin
.oauth
.oauth
import GMGRequestValidator
, GMGRequest
37 from mediagoblin
.oauth
.tools
.request
import decode_authorization_header
38 from mediagoblin
.oauth
.tools
.forms
import WTFormData
39 from mediagoblin
.db
.models
import NonceTimestamp
, Client
, RequestToken
41 # possible client types
42 CLIENT_TYPES
= ["web", "native"] # currently what pump supports
45 def client_register(request
):
46 """ Endpoint for client registration """
48 data
= decode_request(request
)
50 error
= "Could not decode data."
51 return json_response({"error": error
}, status
=400)
54 error
= "Unknown Content-Type"
55 return json_response({"error": error
}, status
=400)
57 if "type" not in data
:
58 error
= "No registration type provided."
59 return json_response({"error": error
}, status
=400)
60 if data
.get("application_type", None) not in CLIENT_TYPES
:
61 error
= "Unknown application_type."
62 return json_response({"error": error
}, status
=400)
64 client_type
= data
["type"]
66 if client_type
== "client_update":
68 if "client_id" not in data
:
69 error
= "client_id is requried to update."
70 return json_response({"error": error
}, status
=400)
71 elif "client_secret" not in data
:
72 error
= "client_secret is required to update."
73 return json_response({"error": error
}, status
=400)
75 client
= Client
.query
.filter_by(
77 secret
=data
["client_secret"]
81 error
= "Unauthorized."
82 return json_response({"error": error
}, status
=403)
84 client
.application_name
= data
.get(
86 client
.application_name
89 client
.application_type
= data
.get(
91 client
.application_type
94 app_name
= ("application_type", client
.application_name
)
95 if app_name
in CLIENT_TYPES
:
96 client
.application_name
= app_name
98 elif client_type
== "client_associate":
100 if "client_id" in data
:
101 error
= "Only set client_id for update."
102 return json_response({"error": error
}, status
=400)
103 elif "access_token" in data
:
104 error
= "access_token not needed for registration."
105 return json_response({"error": error
}, status
=400)
106 elif "client_secret" in data
:
107 error
= "Only set client_secret for update."
108 return json_response({"error": error
}, status
=400)
110 # generate the client_id and client_secret
111 client_id
= random_string(22, UNICODE_ASCII_CHARACTER_SET
)
112 client_secret
= random_string(43, UNICODE_ASCII_CHARACTER_SET
)
113 expirey
= 0 # for now, lets not have it expire
114 expirey_db
= None if expirey
== 0 else expirey
115 application_type
= data
["application_type"]
120 secret
=client_secret
,
122 application_type
=application_type
,
126 error
= "Invalid registration type"
127 return json_response({"error": error
}, status
=400)
129 logo_uri
= data
.get("logo_uri", client
.logo_url
)
130 if logo_uri
is not None and not validate_url(logo_uri
):
131 error
= "Logo URI {0} is not a valid URI.".format(logo_uri
)
132 return json_response(
137 client
.logo_url
= logo_uri
139 client
.application_name
= data
.get("application_name", None)
141 contacts
= data
.get("contacts", None)
142 if contacts
is not None:
143 if not isinstance(contacts
, six
.text_type
):
144 error
= "Contacts must be a string of space-seporated email addresses."
145 return json_response({"error": error
}, status
=400)
147 contacts
= contacts
.split()
148 for contact
in contacts
:
149 if not validate_email(contact
):
151 error
= "Email {0} is not a valid email.".format(contact
)
152 return json_response({"error": error
}, status
=400)
155 client
.contacts
= contacts
157 redirect_uris
= data
.get("redirect_uris", None)
158 if redirect_uris
is not None:
159 if not isinstance(redirect_uris
, six
.text_type
):
160 error
= "redirect_uris must be space-seporated URLs."
161 return json_response({"error": error
}, status
=400)
163 redirect_uris
= redirect_uris
.split()
165 for uri
in redirect_uris
:
166 if not validate_url(uri
):
168 error
= "URI {0} is not a valid URI".format(uri
)
169 return json_response({"error": error
}, status
=400)
171 client
.redirect_uri
= redirect_uris
176 expirey
= 0 if client
.expirey
is None else client
.expirey
178 return json_response(
180 "client_id": client
.id,
181 "client_secret": client
.secret
,
182 "expires_at": expirey
,
186 def request_token(request
):
187 """ Returns request token """
189 data
= decode_request(request
)
191 error
= "Could not decode data."
192 return json_response({"error": error
}, status
=400)
194 if not data
and request
.headers
:
195 data
= request
.headers
197 data
= dict(data
) # mutableifying
199 authorization
= decode_authorization_header(data
)
201 if authorization
== dict() or u
"oauth_consumer_key" not in authorization
:
202 error
= "Missing required parameter."
203 return json_response({"error": error
}, status
=400)
205 # check the client_id
206 client_id
= authorization
[u
"oauth_consumer_key"]
207 client
= Client
.query
.filter_by(id=client_id
).first()
210 # client_id is invalid
211 error
= "Invalid client_id"
212 return json_response({"error": error
}, status
=400)
214 # make request token and return to client
215 request_validator
= GMGRequestValidator(authorization
)
216 rv
= RequestTokenEndpoint(request_validator
)
217 tokens
= rv
.create_request_token(request
, authorization
)
219 # store the nonce & timestamp before we return back
220 nonce
= authorization
[u
"oauth_nonce"]
221 timestamp
= authorization
[u
"oauth_timestamp"]
222 timestamp
= datetime
.datetime
.fromtimestamp(float(timestamp
))
224 nc
= NonceTimestamp(nonce
=nonce
, timestamp
=timestamp
)
227 return form_response(tokens
)
229 @require_active_login
230 def authorize(request
):
231 """ Displays a page for user to authorize """
232 if request
.method
== "POST":
233 return authorize_finish(request
)
236 token
= request
.args
.get("oauth_token", None)
238 # no token supplied, display a html 400 this time
239 err_msg
= _("Must provide an oauth_token.")
240 return render_400(request
, err_msg
=err_msg
)
242 oauth_request
= RequestToken
.query
.filter_by(token
=token
).first()
243 if oauth_request
is None:
244 err_msg
= _("No request token found.")
245 return render_400(request
, err_msg
)
247 if oauth_request
.used
:
248 return authorize_finish(request
)
250 if oauth_request
.verifier
is None:
251 orequest
= GMGRequest(request
)
252 orequest
.resource_owner_key
= token
253 request_validator
= GMGRequestValidator()
254 auth_endpoint
= AuthorizationEndpoint(request_validator
)
255 verifier
= auth_endpoint
.create_verifier(orequest
, {})
256 oauth_request
.verifier
= verifier
["oauth_verifier"]
258 oauth_request
.actor
= request
.user
.id
261 # find client & build context
262 client
= Client
.query
.filter_by(id=oauth_request
.client
).first()
264 authorize_form
= AuthorizeForm(WTFormData({
265 "oauth_token": oauth_request
.token
,
266 "oauth_verifier": oauth_request
.verifier
270 "user": request
.user
,
271 "oauth_request": oauth_request
,
273 "authorize_form": authorize_form
,
277 # AuthorizationEndpoint
278 return render_to_response(
280 "mediagoblin/api/authorize.html",
285 def authorize_finish(request
):
286 """ Finishes the authorize """
288 token
= request
.form
["oauth_token"]
289 verifier
= request
.form
["oauth_verifier"]
290 oauth_request
= RequestToken
.query
.filter_by(token
=token
, verifier
=verifier
)
291 oauth_request
= oauth_request
.first()
293 if oauth_request
is None:
294 # invalid token or verifier
295 err_msg
= _("No request token found.")
296 return render_400(request
, err_msg
)
298 oauth_request
.used
= True
299 oauth_request
.updated
= datetime
.datetime
.now()
302 if oauth_request
.callback
== "oob":
304 context
= {"oauth_request": oauth_request
}
305 return render_to_response(
307 "mediagoblin/api/oob.html",
311 # okay we need to redirect them then!
312 querystring
= "?oauth_token={0}&oauth_verifier={1}".format(
314 oauth_request
.verifier
317 # It's come from the OAuth headers so it'll be encoded.
318 redirect_url
= urllib
.unquote(oauth_request
.callback
)
322 querystring
=querystring
,
323 location
=redirect_url
327 def access_token(request
):
328 """ Provides an access token based on a valid verifier and request token """
329 data
= request
.headers
331 parsed_tokens
= decode_authorization_header(data
)
333 if parsed_tokens
== dict() or "oauth_token" not in parsed_tokens
:
334 error
= "Missing required parameter."
335 return json_response({"error": error
}, status
=400)
337 request
.resource_owner_key
= parsed_tokens
["oauth_consumer_key"]
338 request
.oauth_token
= parsed_tokens
["oauth_token"]
339 request_validator
= GMGRequestValidator(data
)
340 av
= AccessTokenEndpoint(request_validator
)
341 tokens
= av
.create_access_token(request
, {})
342 return form_response(tokens
)