Adds oauth support up until authorization
[mediagoblin.git] / mediagoblin / federation / views.py
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
17 from oauthlib.oauth1 import RequestValidator, RequestTokenEndpoint
18
19 from mediagoblin.tools.translate import pass_to_ugettext
20 from mediagoblin.meddleware.csrf import csrf_exempt
21 from mediagoblin.tools.request import decode_request
22 from mediagoblin.tools.response import json_response, render_400
23 from mediagoblin.tools.crypto import random_string
24 from mediagoblin.tools.validator import validate_email, validate_url
25 from mediagoblin.db.models import Client, RequestToken, AccessToken
26
27 # possible client types
28 client_types = ["web", "native"] # currently what pump supports
29
30 @csrf_exempt
31 def client_register(request):
32 """ Endpoint for client registration """
33 try:
34 data = decode_request(request)
35 except ValueError:
36 error = "Could not decode data."
37 return json_response({"error": error}, status=400)
38
39 if data is "":
40 error = "Unknown Content-Type"
41 return json_response({"error": error}, status=400)
42
43 if "type" not in data:
44 error = "No registration type provided."
45 return json_response({"error": error}, status=400)
46 if data.get("application_type", None) not in client_types:
47 error = "Unknown application_type."
48 return json_response({"error": error}, status=400)
49
50 client_type = data["type"]
51
52 if client_type == "client_update":
53 # updating a client
54 if "client_id" not in data:
55 error = "client_id is requried to update."
56 return json_response({"error": error}, status=400)
57 elif "client_secret" not in data:
58 error = "client_secret is required to update."
59 return json_response({"error": error}, status=400)
60
61 client = Client.query.filter_by(
62 id=data["client_id"],
63 secret=data["client_secret"]
64 ).first()
65
66 if client is None:
67 error = "Unauthorized."
68 return json_response({"error": error}, status=403)
69
70 client.application_name = data.get(
71 "application_name",
72 client.application_name
73 )
74
75 client.application_type = data.get(
76 "application_type",
77 client.application_type
78 )
79
80 app_name = ("application_type", client.application_name)
81 if app_name in client_types:
82 client.application_name = app_name
83
84 elif client_type == "client_associate":
85 # registering
86 if "client_id" in data:
87 error = "Only set client_id for update."
88 return json_response({"error": error}, status=400)
89 elif "access_token" in data:
90 error = "access_token not needed for registration."
91 return json_response({"error": error}, status=400)
92 elif "client_secret" in data:
93 error = "Only set client_secret for update."
94 return json_response({"error": error}, status=400)
95
96 # generate the client_id and client_secret
97 client_id = random_string(22) # seems to be what pump uses
98 client_secret = random_string(43) # again, seems to be what pump uses
99 expirey = 0 # for now, lets not have it expire
100 expirey_db = None if expirey == 0 else expirey
101
102 # save it
103 client = Client(
104 id=client_id,
105 secret=client_secret,
106 expirey=expirey_db,
107 application_type=data["application_type"],
108 )
109
110 else:
111 error = "Invalid registration type"
112 return json_response({"error": error}, status=400)
113
114 logo_url = data.get("logo_url", client.logo_url)
115 if logo_url is not None and not validate_url(logo_url):
116 error = "Logo URL {0} is not a valid URL.".format(logo_url)
117 return json_response(
118 {"error": error},
119 status=400
120 )
121 else:
122 client.logo_url = logo_url
123 application_name=data.get("application_name", None)
124
125 contacts = data.get("contact", None)
126 if contacts is not None:
127 if type(contacts) is not unicode:
128 error = "Contacts must be a string of space-seporated email addresses."
129 return json_response({"error": error}, status=400)
130
131 contacts = contacts.split()
132 for contact in contacts:
133 if not validate_email(contact):
134 # not a valid email
135 error = "Email {0} is not a valid email.".format(contact)
136 return json_response({"error": error}, status=400)
137
138
139 client.contacts = contacts
140
141 request_uri = data.get("request_uris", None)
142 if request_uri is not None:
143 if type(request_uri) is not unicode:
144 error = "redirect_uris must be space-seporated URLs."
145 return json_respinse({"error": error}, status=400)
146
147 request_uri = request_uri.split()
148
149 for uri in request_uri:
150 if not validate_url(uri):
151 # not a valid uri
152 error = "URI {0} is not a valid URI".format(uri)
153 return json_response({"error": error}, status=400)
154
155 client.request_uri = request_uri
156
157
158 client.save()
159
160 expirey = 0 if client.expirey is None else client.expirey
161
162 return json_response(
163 {
164 "client_id": client.id,
165 "client_secret": client.secret,
166 "expires_at": expirey,
167 })
168
169 class ValidationException(Exception):
170 pass
171
172 class GMGRequestValidator(RequestValidator):
173
174 def __init__(self, data):
175 self.POST = data
176
177 def save_request_token(self, token, request):
178 """ Saves request token in db """
179 client_id = self.POST[u"Authorization"][u"oauth_consumer_key"]
180
181 request_token = RequestToken(
182 token=token["oauth_token"],
183 secret=token["oauth_token_secret"],
184 )
185 request_token.client = client_id
186 request_token.save()
187
188
189 @csrf_exempt
190 def request_token(request):
191 """ Returns request token """
192 try:
193 data = decode_request(request)
194 except ValueError:
195 error = "Could not decode data."
196 return json_response({"error": error}, status=400)
197
198 if data is "":
199 error = "Unknown Content-Type"
200 return json_response({"error": error}, status=400)
201
202
203 # Convert 'Authorization' to a dictionary
204 authorization = {}
205 for item in data["Authorization"].split(","):
206 key, value = item.split("=", 1)
207 authorization[key] = value
208 data[u"Authorization"] = authorization
209
210 # check the client_id
211 client_id = data[u"Authorization"][u"oauth_consumer_key"]
212 client = Client.query.filter_by(id=client_id).first()
213 if client is None:
214 # client_id is invalid
215 error = "Invalid client_id"
216 return json_response({"error": error}, status=400)
217
218 request_validator = GMGRequestValidator(data)
219 rv = RequestTokenEndpoint(request_validator)
220 tokens = rv.create_request_token(request, {})
221
222 tokenized = {}
223 for t in tokens.split("&"):
224 key, value = t.split("=")
225 tokenized[key] = value
226
227 # check what encoding to return them in
228 return json_response(tokenized)
229
230 def authorize(request):
231 """ Displays a page for user to authorize """
232 _ = pass_to_ugettext
233 token = request.args.get("oauth_token", None)
234 if token is None:
235 # no token supplied, display a html 400 this time
236 err_msg = _("Must provide an oauth_token")
237 return render_400(request, err_msg=err_msg)
238
239 # AuthorizationEndpoint
240
241
242 @csrf_exempt
243 def access_token(request):
244 """ Provides an access token based on a valid verifier and request token """
245 pass