views.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  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. import datetime
  17. import urllib
  18. import six
  19. from oauthlib.oauth1.rfc5849.utils import UNICODE_ASCII_CHARACTER_SET
  20. from oauthlib.oauth1 import (RequestTokenEndpoint, AuthorizationEndpoint,
  21. AccessTokenEndpoint)
  22. from mediagoblin.decorators import require_active_login
  23. from mediagoblin.tools.translate import pass_to_ugettext
  24. from mediagoblin.meddleware.csrf import csrf_exempt
  25. from mediagoblin.tools.request import decode_request
  26. from mediagoblin.tools.response import (render_to_response, redirect,
  27. json_response, render_400,
  28. form_response)
  29. from mediagoblin.tools.crypto import random_string
  30. from mediagoblin.tools.validator import validate_email, validate_url
  31. from mediagoblin.oauth.forms import AuthorizeForm
  32. from mediagoblin.oauth.oauth import GMGRequestValidator, GMGRequest
  33. from mediagoblin.oauth.tools.request import decode_authorization_header
  34. from mediagoblin.oauth.tools.forms import WTFormData
  35. from mediagoblin.db.models import NonceTimestamp, Client, RequestToken
  36. # possible client types
  37. CLIENT_TYPES = ["web", "native"] # currently what pump supports
  38. @csrf_exempt
  39. def client_register(request):
  40. """ Endpoint for client registration """
  41. try:
  42. data = decode_request(request)
  43. except ValueError:
  44. error = "Could not decode data."
  45. return json_response({"error": error}, status=400)
  46. if data is "":
  47. error = "Unknown Content-Type"
  48. return json_response({"error": error}, status=400)
  49. if "type" not in data:
  50. error = "No registration type provided."
  51. return json_response({"error": error}, status=400)
  52. if data.get("application_type", None) not in CLIENT_TYPES:
  53. error = "Unknown application_type."
  54. return json_response({"error": error}, status=400)
  55. client_type = data["type"]
  56. if client_type == "client_update":
  57. # updating a client
  58. if "client_id" not in data:
  59. error = "client_id is requried to update."
  60. return json_response({"error": error}, status=400)
  61. elif "client_secret" not in data:
  62. error = "client_secret is required to update."
  63. return json_response({"error": error}, status=400)
  64. client = Client.query.filter_by(
  65. id=data["client_id"],
  66. secret=data["client_secret"]
  67. ).first()
  68. if client is None:
  69. error = "Unauthorized."
  70. return json_response({"error": error}, status=403)
  71. client.application_name = data.get(
  72. "application_name",
  73. client.application_name
  74. )
  75. client.application_type = data.get(
  76. "application_type",
  77. client.application_type
  78. )
  79. app_name = ("application_type", client.application_name)
  80. if app_name in CLIENT_TYPES:
  81. client.application_name = app_name
  82. elif client_type == "client_associate":
  83. # registering
  84. if "client_id" in data:
  85. error = "Only set client_id for update."
  86. return json_response({"error": error}, status=400)
  87. elif "access_token" in data:
  88. error = "access_token not needed for registration."
  89. return json_response({"error": error}, status=400)
  90. elif "client_secret" in data:
  91. error = "Only set client_secret for update."
  92. return json_response({"error": error}, status=400)
  93. # generate the client_id and client_secret
  94. client_id = random_string(22, UNICODE_ASCII_CHARACTER_SET)
  95. client_secret = random_string(43, UNICODE_ASCII_CHARACTER_SET)
  96. expirey = 0 # for now, lets not have it expire
  97. expirey_db = None if expirey == 0 else expirey
  98. application_type = data["application_type"]
  99. # save it
  100. client = Client(
  101. id=client_id,
  102. secret=client_secret,
  103. expirey=expirey_db,
  104. application_type=application_type,
  105. )
  106. else:
  107. error = "Invalid registration type"
  108. return json_response({"error": error}, status=400)
  109. logo_uri = data.get("logo_uri", client.logo_url)
  110. if logo_uri is not None and not validate_url(logo_uri):
  111. error = "Logo URI {0} is not a valid URI.".format(logo_uri)
  112. return json_response(
  113. {"error": error},
  114. status=400
  115. )
  116. else:
  117. client.logo_url = logo_uri
  118. client.application_name = data.get("application_name", None)
  119. contacts = data.get("contacts", None)
  120. if contacts is not None:
  121. if not isinstance(contacts, six.text_type):
  122. error = "Contacts must be a string of space-seporated email addresses."
  123. return json_response({"error": error}, status=400)
  124. contacts = contacts.split()
  125. for contact in contacts:
  126. if not validate_email(contact):
  127. # not a valid email
  128. error = "Email {0} is not a valid email.".format(contact)
  129. return json_response({"error": error}, status=400)
  130. client.contacts = contacts
  131. redirect_uris = data.get("redirect_uris", None)
  132. if redirect_uris is not None:
  133. if not isinstance(redirect_uris, six.text_type):
  134. error = "redirect_uris must be space-seporated URLs."
  135. return json_response({"error": error}, status=400)
  136. redirect_uris = redirect_uris.split()
  137. for uri in redirect_uris:
  138. if not validate_url(uri):
  139. # not a valid uri
  140. error = "URI {0} is not a valid URI".format(uri)
  141. return json_response({"error": error}, status=400)
  142. client.redirect_uri = redirect_uris
  143. client.save()
  144. expirey = 0 if client.expirey is None else client.expirey
  145. return json_response(
  146. {
  147. "client_id": client.id,
  148. "client_secret": client.secret,
  149. "expires_at": expirey,
  150. })
  151. @csrf_exempt
  152. def request_token(request):
  153. """ Returns request token """
  154. try:
  155. data = decode_request(request)
  156. except ValueError:
  157. error = "Could not decode data."
  158. return json_response({"error": error}, status=400)
  159. if not data and request.headers:
  160. data = request.headers
  161. data = dict(data) # mutableifying
  162. authorization = decode_authorization_header(data)
  163. if authorization == dict() or u"oauth_consumer_key" not in authorization:
  164. error = "Missing required parameter."
  165. return json_response({"error": error}, status=400)
  166. # check the client_id
  167. client_id = authorization[u"oauth_consumer_key"]
  168. client = Client.query.filter_by(id=client_id).first()
  169. if client == None:
  170. # client_id is invalid
  171. error = "Invalid client_id"
  172. return json_response({"error": error}, status=400)
  173. # make request token and return to client
  174. request_validator = GMGRequestValidator(authorization)
  175. rv = RequestTokenEndpoint(request_validator)
  176. tokens = rv.create_request_token(request, authorization)
  177. # store the nonce & timestamp before we return back
  178. nonce = authorization[u"oauth_nonce"]
  179. timestamp = authorization[u"oauth_timestamp"]
  180. timestamp = datetime.datetime.fromtimestamp(float(timestamp))
  181. nc = NonceTimestamp(nonce=nonce, timestamp=timestamp)
  182. nc.save()
  183. return form_response(tokens)
  184. @require_active_login
  185. def authorize(request):
  186. """ Displays a page for user to authorize """
  187. if request.method == "POST":
  188. return authorize_finish(request)
  189. _ = pass_to_ugettext
  190. token = request.args.get("oauth_token", None)
  191. if token is None:
  192. # no token supplied, display a html 400 this time
  193. err_msg = _("Must provide an oauth_token.")
  194. return render_400(request, err_msg=err_msg)
  195. oauth_request = RequestToken.query.filter_by(token=token).first()
  196. if oauth_request is None:
  197. err_msg = _("No request token found.")
  198. return render_400(request, err_msg)
  199. if oauth_request.used:
  200. return authorize_finish(request)
  201. if oauth_request.verifier is None:
  202. orequest = GMGRequest(request)
  203. orequest.resource_owner_key = token
  204. request_validator = GMGRequestValidator()
  205. auth_endpoint = AuthorizationEndpoint(request_validator)
  206. verifier = auth_endpoint.create_verifier(orequest, {})
  207. oauth_request.verifier = verifier["oauth_verifier"]
  208. oauth_request.user = request.user.id
  209. oauth_request.save()
  210. # find client & build context
  211. client = Client.query.filter_by(id=oauth_request.client).first()
  212. authorize_form = AuthorizeForm(WTFormData({
  213. "oauth_token": oauth_request.token,
  214. "oauth_verifier": oauth_request.verifier
  215. }))
  216. context = {
  217. "user": request.user,
  218. "oauth_request": oauth_request,
  219. "client": client,
  220. "authorize_form": authorize_form,
  221. }
  222. # AuthorizationEndpoint
  223. return render_to_response(
  224. request,
  225. "mediagoblin/api/authorize.html",
  226. context
  227. )
  228. def authorize_finish(request):
  229. """ Finishes the authorize """
  230. _ = pass_to_ugettext
  231. token = request.form["oauth_token"]
  232. verifier = request.form["oauth_verifier"]
  233. oauth_request = RequestToken.query.filter_by(token=token, verifier=verifier)
  234. oauth_request = oauth_request.first()
  235. if oauth_request is None:
  236. # invalid token or verifier
  237. err_msg = _("No request token found.")
  238. return render_400(request, err_msg)
  239. oauth_request.used = True
  240. oauth_request.updated = datetime.datetime.now()
  241. oauth_request.save()
  242. if oauth_request.callback == "oob":
  243. # out of bounds
  244. context = {"oauth_request": oauth_request}
  245. return render_to_response(
  246. request,
  247. "mediagoblin/api/oob.html",
  248. context
  249. )
  250. # okay we need to redirect them then!
  251. querystring = "?oauth_token={0}&oauth_verifier={1}".format(
  252. oauth_request.token,
  253. oauth_request.verifier
  254. )
  255. # It's come from the OAuth headers so it'll be encoded.
  256. redirect_url = urllib.unquote(oauth_request.callback)
  257. return redirect(
  258. request,
  259. querystring=querystring,
  260. location=redirect_url
  261. )
  262. @csrf_exempt
  263. def access_token(request):
  264. """ Provides an access token based on a valid verifier and request token """
  265. data = request.headers
  266. parsed_tokens = decode_authorization_header(data)
  267. if parsed_tokens == dict() or "oauth_token" not in parsed_tokens:
  268. error = "Missing required parameter."
  269. return json_response({"error": error}, status=400)
  270. request.resource_owner_key = parsed_tokens["oauth_consumer_key"]
  271. request.oauth_token = parsed_tokens["oauth_token"]
  272. request_validator = GMGRequestValidator(data)
  273. av = AccessTokenEndpoint(request_validator)
  274. tokens = av.create_access_token(request, {})
  275. return form_response(tokens)