21 | '''
22 |
23 | from datetime import datetime
24 | import base64
25 | import pprint
26 |
27 | try:
28 | from functools import wraps
29 | except ImportError:
30 | from django.utils.functional import wraps # Python 2.3, 2.4 fallback.
31 |
32 | try:
33 | import json # New in Python v2.6
34 | except ImportError:
35 | from django.utils import simplejson as json
36 |
37 | from django.conf import settings
38 | from django.core.exceptions import PermissionDenied
39 | from django.http import HttpResponse, HttpResponseBadRequest, \
40 | HttpResponseForbidden
41 | from django.contrib.auth import authenticate, login, logout
42 |
43 | from weave.utils import weave_timestamp, make_sync_hash
44 | from weave import Logging
45 |
46 |
47 | logger = Logging.get_logger()
48 |
49 |
50 | def view_or_basicauth(view, request, test_func, realm="", *args, **kwargs):
51 | """
52 | This is a helper function used by both 'logged_in_or_basicauth' and
53 | 'has_perm_or_basicauth' that does the nitty of determining if they
54 | are already logged in or if they have provided proper http-authorization
55 | and returning the view if all goes well, otherwise responding with a 401.
56 | """
57 | if test_func(request.user):
58 | # Already logged in, just return the view.
59 | return view(request, *args, **kwargs)
60 |
61 | if settings.WEAVE.DISABLE_LOGIN:
62 | # disable weave own basicauth login, user must login before
63 | # using firefox-sync e.g. use the django admin login page.
64 | msg = "Request forbidden, basicauth was disabled."
65 | logger.debug(msg)
66 | if settings.DEBUG:
67 | return HttpResponseForbidden(msg)
68 | else:
69 | return HttpResponseForbidden()
70 |
71 | # They are not logged in. See if they provided login credentials
72 | if 'HTTP_AUTHORIZATION' in request.META:
73 | logger.debug("HTTP_AUTHORIZATION: %r" % request.META['HTTP_AUTHORIZATION'])
74 | # NOTE: We are only support basic authentication for now.
75 | auth = request.META['HTTP_AUTHORIZATION'].split()
76 | if len(auth) != 2:
77 | logger.debug("HTTP_AUTHORIZATION wrong length.")
78 | return HttpResponseBadRequest()
79 |
80 | auth_type, auth_data = auth
81 |
82 | if auth_type.lower() != "basic":
83 | logger.debug("HTTP_AUTHORIZATION is not 'basic'")
84 | return HttpResponseBadRequest()
85 |
86 | username, password = base64.b64decode(auth_data).split(':')
87 |
88 | # username = _fix_username(username)
89 | # if len(username) > 30:
90 | # logger.error("Username %r is longer than 30 characters!" % username)
91 | # return HttpResponseBadRequest()
92 |
93 | if len(password) > 256:
94 | logger.error("Password %r is longer than 256 characters!" % password)
95 | return HttpResponseBadRequest()
96 |
97 | user = authenticate(username=username, password=password)
98 | if user is None:
99 | logger.debug("basicauth error: user %r unknown or password wrong." % username)
100 | else:
101 | if not user.is_active:
102 | logger.debug("basicauth error: user %r is not active." % username)
103 | else:
104 | login(request, user)
105 | request.user = user
106 | logger.debug("basicauth success: user %r logged in." % username)
107 | return view(request, *args, **kwargs)
108 |
109 | # Either they did not provide an authorization header or
110 | # something in the authorization attempt failed. Send a 401
111 | # back to them to ask them to authenticate.
112 | logger.debug("No HTTP_AUTHORIZATION send, yet.")
113 | response = HttpResponse()
114 | response.status_code = 401 # Unauthorized: request requires user authentication
115 | response['WWW-Authenticate'] = 'Basic realm="%s"' % realm
116 | return response
117 |
118 |
119 | def logged_in_or_basicauth(func, realm=settings.WEAVE.BASICAUTH_REALM):
120 | """
121 | A simple decorator that requires a user to be logged in. If they are not
122 | logged in the request is examined for a 'authorization' header.
123 |
124 | If the header is present it is tested for basic authentication and
125 | the user is logged in with the provided credentials.
126 |
127 | If the header is not present a http 401 is sent back to the
128 | requestor to provide credentials.
129 |
130 | The purpose of this is that in several django projects I have needed
131 | several specific views that need to support basic authentication, yet the
132 | web site as a whole used django's provided authentication.
133 |
134 | The uses for this are for urls that are access programmatically such as
135 | by rss feed readers, yet the view requires a user to be logged in. Many rss
136 | readers support supplying the authentication credentials via http basic
137 | auth (and they do NOT support a redirect to a form where they post a
138 | username/password.)
139 |
140 | Use is simple:
141 |
142 | @logged_in_or_basicauth
143 | def your_view:
144 | ...
145 |
146 | You can provide the name of the realm to ask for authentication within.
147 | """
148 | @wraps(func)
149 | def wrapper(request, *args, **kwargs):
150 | return view_or_basicauth(func, request,
151 | lambda u: u.is_authenticated(),
152 | realm, *args, **kwargs)
153 | return wrapper
154 |
155 |
156 | def has_perm_or_basicauth(func, perm, realm=""):
157 | """
158 | This is similar to the above decorator 'logged_in_or_basicauth'
159 | except that it requires the logged in user to have a specific
160 | permission.
161 |
162 | Use:
163 |
164 | @logged_in_or_basicauth('asforums.view_forumcollection')
165 | def your_view:
166 | ...
167 |
168 | """
169 | @wraps(func)
170 | def wrapper(request, *args, **kwargs):
171 | return view_or_basicauth(func, request,
172 | lambda u: u.has_perm(perm),
173 | realm, *args, **kwargs)
174 | return wrapper
175 |
176 |
177 | def weave_assert_username(func, key='username'):
178 | """
179 | Decorator to check if the username from the URL is the one logged in.
180 | It uses the kwargs "username" as the default key but it can be changed by passing
181 | key='field' to the decorator.
182 |
183 | Use:
184 |
185 | @weave_assert_username
186 | def your_view(request, username):
187 |
188 | You can provide the key of the username field which is 'username' by default.
189 | """
190 | @wraps(func)
191 | def wrapper(request, *args, **kwargs):
192 | # Test if username argument matches logged in user.
193 | # Weave uses lowercase usernames inside the URL!!!
194 |
195 | url_username = kwargs[key].lower()
196 | logger.debug("Raw userdata from url: %r" % url_username)
197 |
198 | if request.user.username.lower() == url_username:
199 | # XXX obsolete weave 1.0 API ?
200 | logger.debug("Plaintext username %r from url is ok." % url_username)
201 | return func(request, *args, **kwargs)
202 |
203 | if not len(url_username) == 32:
204 | msg = "Wrong length of url userdata: %i" % len(url_username)
205 | logger.debug(msg + "(should be 32 characters long)")
206 | raise PermissionDenied(msg)
207 |
208 | # check new API
209 | email = request.user.email
210 | sync_hash = make_sync_hash(email)
211 | if url_username.startswith(sync_hash):
212 | logger.debug("Email hash %r from url is ok." % url_username)
213 | return func(request, *args, **kwargs)
214 |
215 | logger.debug("Url userdata %r doesn't fit to user %s" % (url_username, request.user.username))
216 |
217 | logger.info("Logout user %s" % request.user)
218 | logout(request)
219 |
220 | raise PermissionDenied("URL userdata doesn't fit to user from HTTP authentication.")
221 |
222 | return wrapper
223 |
224 |
225 | def weave_assert_version(version):
226 | """
227 | Check the weave api version (comes from the url).
228 |
229 | Use:
230 |
231 | @weave_assert_version
232 | def your_view(request, username):
233 | """
234 | def decorator(func):
235 | @wraps(func)
236 | def wrapper(request, *args, **kwargs):
237 | if not 'version' in kwargs:
238 | msg = "no version specified in URL"
239 | logger.error(msg)
240 | raise AssertionError(msg)
241 |
242 | url_version = kwargs['version']
243 | if isinstance(version, (list, tuple)) and url_version in version:
244 | return func(request, *args, **kwargs)
245 | elif url_version == version:
246 | return func(request, *args, **kwargs)
247 |
248 | msg = "unsupported weave client version: %r" % url_version
249 | logger.error(msg)
250 | raise AssertionError(msg)
251 |
252 | return wrapper
253 | return decorator
254 |
255 |
256 | def weave_render_response(func):
257 | """
258 | Decorator that checks for the presence of weave specific HTTP headers
259 | and formats output accordingly.
260 |
261 | Use:
262 |
263 | @weave_render_response
264 | def your_view(request):
265 | """
266 | @wraps(func)
267 | def wrapper(request, *args, **kwargs):
268 | timedata = datetime.now()
269 | data = func(request, timestamp=timedata, *args, **kwargs)
270 | logger.debug("Raw response data for %r: %r" % (func.__name__, data))
271 | response = HttpResponse()
272 | response["X-Weave-Timestamp"] = weave_timestamp(timedata)
273 |
274 | if settings.DEBUG and "debug" in request.GET:
275 | logger.debug("debug output for %r:" % func.__name__)
276 |
277 | if int(request.GET["debug"]) > 1:
278 | def load_payload(item):
279 | if "payload" in item:
280 | raw_payload = item["payload"]
281 | payload_dict = json.loads(raw_payload)
282 | item["payload"] = payload_dict
283 | return item
284 |
285 | if isinstance(data, list):
286 | data = [load_payload(item) for item in data]
287 | else:
288 | data = load_payload(data)
289 |
290 | response["content-type"] = "text/plain"
291 | response.content = json.dumps(data, indent=4)
292 | else:
293 | if request.META.get("HTTP_ACCEPT") == 'application/newlines' and isinstance(data, list):
294 | response.content = '\n'.join([json.dumps(element) for element in data]) + '\n'
295 | response["content-type"] = 'application/newlines'
296 | response['X-Weave-Records'] = len(data)
297 | else:
298 | response["content-type"] = "application/json"
299 | response.content = json.dumps(data)
300 |
301 | return response
302 | return wrapper
303 |
304 |
305 | def debug_sync_request(func):
306 | @wraps(func)
307 | def wrapper(request, *args, **kwargs):
308 | if not settings.WEAVE.DEBUG_REQUEST:
309 | return func(request, *args, **kwargs)
310 |
311 | logger.debug("view args: %s" % repr(args))
312 | logger.debug("view kwargs: %s" % repr(kwargs))
313 |
314 | response = func(request, *args, **kwargs)
315 |
316 | logger.debug("request.GET: %s" % repr(request.GET))
317 | logger.debug("request.POST: %s" % pprint.pformat(request.POST))
318 | logger.debug("response.status_code: %r" % response.status_code)
319 | logger.debug("response headers: %s" % repr(response.items()))
320 | logger.debug("response raw content: %s" % repr(response.content))
321 |
322 | if response["content-type"] == "application/json":
323 | data = json.loads(response.content)
324 | logger.debug("content: %s" % json.dumps(data, indent=4))
325 |
326 | return response
327 | return wrapper
328 |
329 |
330 |
--------------------------------------------------------------------------------
/weave/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | import datetime
3 | from south.db import db
4 | from south.v2 import SchemaMigration
5 | from django.db import models
6 |
7 | class Migration(SchemaMigration):
8 |
9 | def forwards(self, orm):
10 |
11 | # Adding model 'Collection'
12 | db.create_table('weave_collection', (
13 | ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
14 | ('modified', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
15 | ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
16 | ('name', self.gf('django.db.models.fields.CharField')(max_length=96)),
17 | ('site', self.gf('django.db.models.fields.related.ForeignKey')(default=1, to=orm['sites.Site'])),
18 | ))
19 | db.send_create_signal('weave', ['Collection'])
20 |
21 | # Adding model 'Wbo'
22 | db.create_table('weave_wbo', (
23 | ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
24 | ('modified', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
25 | ('collection', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['weave.Collection'], null=True, blank=True)),
26 | ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
27 | ('wboid', self.gf('django.db.models.fields.CharField')(max_length=64)),
28 | ('parentid', self.gf('django.db.models.fields.CharField')(max_length=64, null=True, blank=True)),
29 | ('predecessorid', self.gf('django.db.models.fields.CharField')(max_length=64, null=True, blank=True)),
30 | ('sortindex', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)),
31 | ('payload', self.gf('django.db.models.fields.TextField')(blank=True)),
32 | ))
33 | db.send_create_signal('weave', ['Wbo'])
34 |
35 |
36 | def backwards(self, orm):
37 |
38 | # Deleting model 'Collection'
39 | db.delete_table('weave_collection')
40 |
41 | # Deleting model 'Wbo'
42 | db.delete_table('weave_wbo')
43 |
44 |
45 | models = {
46 | 'auth.group': {
47 | 'Meta': {'object_name': 'Group'},
48 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
49 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
50 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
51 | },
52 | 'auth.permission': {
53 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
54 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
55 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
56 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
57 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
58 | },
59 | 'auth.user': {
60 | 'Meta': {'object_name': 'User'},
61 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
62 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
63 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
64 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
65 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
66 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
67 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
68 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
69 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
70 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
71 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
72 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
73 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
74 | },
75 | 'contenttypes.contenttype': {
76 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
77 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
78 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
79 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
80 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
81 | },
82 | 'sites.site': {
83 | 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"},
84 | 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
85 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
86 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
87 | },
88 | 'weave.collection': {
89 | 'Meta': {'ordering': "('-modified',)", 'object_name': 'Collection'},
90 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
91 | 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
92 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '96'}),
93 | 'site': ('django.db.models.fields.related.ForeignKey', [], {'default': '1', 'to': "orm['sites.Site']"}),
94 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
95 | },
96 | 'weave.wbo': {
97 | 'Meta': {'ordering': "('-modified',)", 'object_name': 'Wbo'},
98 | 'collection': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['weave.Collection']", 'null': 'True', 'blank': 'True'}),
99 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
100 | 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
101 | 'parentid': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}),
102 | 'payload': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
103 | 'predecessorid': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}),
104 | 'sortindex': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
105 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
106 | 'wboid': ('django.db.models.fields.CharField', [], {'max_length': '64'})
107 | }
108 | }
109 |
110 | complete_apps = ['weave']
111 |
--------------------------------------------------------------------------------
/weave/migrations/0002_add_field_wbo_ttl.py:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | import datetime
3 | from south.db import db
4 | from south.v2 import SchemaMigration
5 | from django.db import models
6 |
7 | class Migration(SchemaMigration):
8 |
9 | def forwards(self, orm):
10 |
11 | # Adding field 'Wbo.ttl'
12 | db.add_column('weave_wbo', 'ttl', models.IntegerField(null=True, blank=True), keep_default=False)
13 |
14 |
15 | def backwards(self, orm):
16 |
17 | # Deleting field 'Wbo.ttl'
18 | db.delete_column('weave_wbo', 'ttl')
19 |
20 |
21 | models = {
22 | 'auth.group': {
23 | 'Meta': {'object_name': 'Group'},
24 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
25 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
26 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
27 | },
28 | 'auth.permission': {
29 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
30 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
31 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
32 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
33 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
34 | },
35 | 'auth.user': {
36 | 'Meta': {'object_name': 'User'},
37 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
38 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
39 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
40 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
41 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
42 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
43 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
44 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
45 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
46 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
47 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
48 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
49 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
50 | },
51 | 'contenttypes.contenttype': {
52 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
53 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
54 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
55 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
56 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
57 | },
58 | 'sites.site': {
59 | 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"},
60 | 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
61 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
62 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
63 | },
64 | 'weave.collection': {
65 | 'Meta': {'ordering': "('-modified',)", 'object_name': 'Collection'},
66 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
67 | 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
68 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '96'}),
69 | 'site': ('django.db.models.fields.related.ForeignKey', [], {'default': '1', 'to': "orm['sites.Site']"}),
70 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
71 | },
72 | 'weave.wbo': {
73 | 'Meta': {'ordering': "('-modified',)", 'object_name': 'Wbo'},
74 | 'collection': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['weave.Collection']", 'null': 'True', 'blank': 'True'}),
75 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
76 | 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
77 | 'parentid': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}),
78 | 'payload': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
79 | 'payload_size': ('django.db.models.fields.PositiveIntegerField', [], {}),
80 | 'predecessorid': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}),
81 | 'sortindex': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
82 | 'ttl': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
83 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
84 | 'wboid': ('django.db.models.fields.CharField', [], {'max_length': '64'})
85 | }
86 | }
87 |
88 | complete_apps = ['weave']
89 |
--------------------------------------------------------------------------------
/weave/migrations/0003_add_field_payload_size.py:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | import datetime
3 | from south.db import db
4 | from south.v2 import DataMigration
5 | from django.db import models
6 |
7 | class Migration(DataMigration):
8 |
9 | def forwards(self, orm):
10 | # Adding field 'Wbo.payload_size'
11 | db.add_column('weave_wbo', 'payload_size', self.gf('django.db.models.fields.PositiveIntegerField')(default=0), keep_default=False)
12 |
13 | # Calculate payload size for existing entries
14 | for wbo in orm.Wbo.objects.all():
15 | payload = wbo.payload
16 | payload_size = len(payload)
17 | wbo.payload_size = payload_size
18 | wbo.save()
19 |
20 |
21 | def backwards(self, orm):
22 | # Deleting field 'Wbo.payload_size'
23 | db.delete_column('weave_wbo', 'payload_size')
24 |
25 |
26 | models = {
27 | 'auth.group': {
28 | 'Meta': {'object_name': 'Group'},
29 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
30 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
31 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
32 | },
33 | 'auth.permission': {
34 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
35 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
36 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
37 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
38 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
39 | },
40 | 'auth.user': {
41 | 'Meta': {'object_name': 'User'},
42 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
43 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
44 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
45 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
46 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
47 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
48 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
49 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
50 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
51 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
52 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
53 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
54 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
55 | },
56 | 'contenttypes.contenttype': {
57 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
58 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
59 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
60 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
61 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
62 | },
63 | 'sites.site': {
64 | 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"},
65 | 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
66 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
67 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
68 | },
69 | 'weave.collection': {
70 | 'Meta': {'ordering': "('-modified',)", 'object_name': 'Collection'},
71 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
72 | 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
73 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '96'}),
74 | 'site': ('django.db.models.fields.related.ForeignKey', [], {'default': '1', 'to': "orm['sites.Site']"}),
75 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
76 | },
77 | 'weave.wbo': {
78 | 'Meta': {'ordering': "('-modified',)", 'object_name': 'Wbo'},
79 | 'collection': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['weave.Collection']", 'null': 'True', 'blank': 'True'}),
80 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
81 | 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
82 | 'parentid': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}),
83 | 'payload': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
84 | 'payload_size': ('django.db.models.fields.PositiveIntegerField', [], {}),
85 | 'predecessorid': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}),
86 | 'sortindex': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
87 | 'ttl': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
88 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
89 | 'wboid': ('django.db.models.fields.CharField', [], {'max_length': '64'})
90 | }
91 | }
92 |
93 | complete_apps = ['weave']
94 |
--------------------------------------------------------------------------------
/weave/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/discontinue/django-sync-server/afe98dca07f08e36280143a2be9b8c4bad11fb81/weave/migrations/__init__.py
--------------------------------------------------------------------------------
/weave/models.py:
--------------------------------------------------------------------------------
1 | '''
2 | Models.
3 | FIXME: I dropped site framework integration for the sake
4 | of simpler debugging. Add it back once API is stable
5 | and working.
6 |
7 | Created on 15.03.2010
8 |
9 | @license: GNU GPL v3 or above, see LICENSE for more details.
10 | @copyright: 2010-2013 see AUTHORS for more details.
11 | @author: Jens Diemer
12 | @author: FladischerMichael
13 | '''
14 |
15 | from django.conf import settings
16 | from django.db import models
17 | from django.core.exceptions import ValidationError
18 | from django.contrib.auth.models import User
19 | from django.contrib.sites.models import Site
20 | from django.contrib.sites.managers import CurrentSiteManager
21 |
22 | from weave.utils import weave_timestamp
23 | from weave import Logging
24 |
25 | logger = Logging.get_logger()
26 |
27 |
28 | class BaseModel(models.Model):
29 | modified = models.DateTimeField(auto_now=True, help_text="Time of the last change.")
30 |
31 | class Meta:
32 | abstract = True
33 |
34 |
35 | class CollectionManager(CurrentSiteManager):
36 | def create_or_update(self, user, col_name, timestamp, since=None):
37 | logger.debug("Created or update collection %s for user %s" % (col_name, user.username))
38 |
39 | collection, created = super(CollectionManager, self).get_or_create(
40 | user=user, name=col_name,
41 | )
42 |
43 | # See if we have a constraint on the last modified date
44 | if since is not None:
45 | if since < collection.modified:
46 | raise ValidationError
47 |
48 | collection.modified = timestamp
49 | collection.save()
50 |
51 | if created:
52 | logger.debug("New collection %s created." % collection)
53 | else:
54 | logger.debug("Existing collection %s updated." % collection)
55 | return collection, created
56 |
57 |
58 | class Collection(BaseModel):
59 | """
60 | http://docs.services.mozilla.com/storage/apis-1.1.html
61 |
62 | inherited from BaseModel:
63 | modified -> datetime of the last change
64 | """
65 | user = models.ForeignKey(User)
66 | name = models.CharField(max_length=96)
67 |
68 | site = models.ForeignKey(Site, editable=False, default=settings.SITE_ID)
69 | on_site = CollectionManager('site')
70 |
71 | def __unicode__(self):
72 | return u"%r (user: %r, site: %r)" % (self.name, self.user.username, self.site)
73 |
74 | class Meta:
75 | ordering = ("-modified",)
76 |
77 |
78 | class WboManager(models.Manager):
79 | def create_or_update(self, payload_dict, collection, user, timestamp):
80 | """
81 | create or update a wbo
82 | TODO:
83 | - Check parentid, but how?
84 | - must wboid + parentid be unique?
85 | """
86 | logger.debug("Created or update WBO for collection %s" % collection)
87 |
88 | payload = payload_dict['payload']
89 | payload_size = len(payload)
90 |
91 | wbo, created = Wbo.objects.get_or_create(
92 | collection=collection,
93 | user=user,
94 | wboid=payload_dict['id'],
95 | defaults={
96 | 'parentid': payload_dict.get('parentid', None),
97 | 'predecessorid': payload_dict.get('predecessorid', None),
98 | 'sortindex': payload_dict.get('sortindex', None),
99 | 'ttl': payload_dict.get('ttl', None),
100 | 'modified': timestamp,
101 | 'payload_size': payload_size,
102 | 'payload': payload,
103 | }
104 | )
105 | if created:
106 | logger.debug("New wbo created: %r" % wbo)
107 | else:
108 | wbo.parentid = payload_dict.get("parentid", None)
109 | wbo.predecessorid = payload_dict.get("predecessorid", None)
110 | wbo.sortindex = payload_dict.get("sortindex", None)
111 | wbo.ttl = payload_dict.get("ttl", None)
112 | wbo.modified = timestamp
113 | wbo.payload_size = payload_size
114 | wbo.payload = payload
115 | wbo.save()
116 | logger.debug("Existing wbo updated: %r" % wbo)
117 |
118 | return wbo, created
119 |
120 |
121 | class Wbo(BaseModel):
122 | """
123 | http://docs.services.mozilla.com/storage/apis-1.1.html
124 |
125 | inherited from BaseModel:
126 | modified -> datetime of the last change
127 | """
128 | objects = WboManager()
129 | collection = models.ForeignKey(Collection, blank=True, null=True)
130 | user = models.ForeignKey(User)
131 | wboid = models.CharField(max_length=64,
132 | help_text="wbo identifying string"
133 | )
134 | parentid = models.CharField(max_length=64, blank=True, null=True,
135 | help_text="wbo parent identifying string"
136 | )
137 | predecessorid = models.CharField(max_length=64, blank=True, null=True,
138 | help_text="wbo predecessorid"
139 | )
140 | sortindex = models.IntegerField(blank=True, null=True,
141 | help_text="An integer indicting the relative importance of this item in the collection."
142 | )
143 | payload = models.TextField(blank=True,
144 | help_text=(
145 | "A string containing a JSON structure encapsulating the data of the record."
146 | " This structure is defined separately for each WBO type. Parts of the"
147 | " structure may be encrypted, in which case the structure should also"
148 | " specify a record for decryption."
149 | )
150 | )
151 | payload_size = models.PositiveIntegerField(help_text="Size of the payload.")
152 | ttl = models.IntegerField(blank=True, null=True,
153 | help_text=(
154 | "The number of seconds to keep this record."
155 | " After that time, this item will not be returned."
156 | )
157 | )
158 |
159 | def clean(self):
160 | # Don't allow draft entries to have a pub_date.
161 | if self.ttl is not None:
162 | if self.ttl < 0 or self.ttl > 31536000:
163 | # from https://hg.mozilla.org/services/server-storage/file/830a414aaed7/syncstorage/wbo.py#l80
164 | raise ValidationError('TTL %r out of range.' % self.ttl)
165 |
166 | def save(self, *args, **kwarg):
167 | self.full_clean()
168 | super(Wbo, self).save(*args, **kwarg)
169 |
170 | def get_response_dict(self):
171 | response_dict = {
172 | "id": self.wboid,
173 | "modified": weave_timestamp(self.modified),
174 | "payload": self.payload,
175 | }
176 | # Don't send additional properties if payload is emtpy -> deleted Wbo
177 | if self.payload != '':
178 | for key in ("parentid", "predecessorid", "sortindex"):
179 | value = getattr(self, key)
180 | if value:
181 | response_dict[key] = value
182 | return response_dict
183 |
184 | def __unicode__(self):
185 | return u"%r (%r)" % (self.wboid, self.collection)
186 |
187 | class Meta:
188 | ordering = ("-modified",)
189 |
--------------------------------------------------------------------------------
/weave/templates/404.html:
--------------------------------------------------------------------------------
1 | {% extends "admin/base_site.html" %}
2 |
3 | {% block title %}django-sync-server - Page not found (404){% endblock %}
4 |
5 | {% block content %}
6 | Page not found (404)
7 | We're sorry, but the requested object could not be found.
8 | {% endblock %}
--------------------------------------------------------------------------------
/weave/templates/500.html:
--------------------------------------------------------------------------------
1 | {% extends "admin/base_site.html" %}
2 |
3 | {% block title %}django-sync-server - Server error (500){% endblock %}
4 |
5 | {% block content %}
6 | Server Error (500)
7 | There's been an error. It's been reported to the site administrators via e-mail and should be fixed shortly. Thanks for your patience.
8 | You see not the debug error page, because debugging is off or your IP is not in settings.INTERNAL_IPS
9 | {% endblock %}
--------------------------------------------------------------------------------
/weave/templates/weave/info_page.html:
--------------------------------------------------------------------------------
1 | {% extends "admin/base_site.html" %}
2 |
3 | {% block branding %}
4 | {{ title }}
5 | {% endblock %}
6 |
7 | {% block content %}
8 |
9 | Use {{ server_url }} as server url in your weave client preferences.
10 |
11 |
12 | {% if request.user.is_authenticated %}
13 | You logged in as {{ request.user }}.
14 | User statistics
15 |
16 | - WBO count
17 | - {{ wbo_count }}
18 | - Payload size together
19 | - {{ payload_size|filesizeformat }}
20 | - Latest entry
21 | - {{ latest_modified|timesince }}
22 | - Oldest entry
23 | - {{ oldest_modified|timesince }}
24 |
25 | (Calculated in {{ duration|stringformat:".2f" }}sec. )
26 | {% else %}
27 | You not logged in. No statistics available for anonymous user.
28 | {% endif %}
29 |
30 | How to enable sync debug, visit DebugHelp wiki page.
31 |
32 | {% endblock content %}
33 |
34 | {% block footer %}
35 |
38 | {% endblock %}
--------------------------------------------------------------------------------
/weave/tests.py:
--------------------------------------------------------------------------------
1 | #coding:utf-8
2 |
3 | """
4 | django-sync-server unittests
5 | ~~~~~~~~~~~~~~~~~~~~~~
6 |
7 | :copyleft: 2010 by the django-sync-server team, see AUTHORS for more details.
8 | :license: GNU GPL v3 or above, see LICENSE for more details.
9 | """
10 |
11 | import base64
12 | import logging
13 | import time
14 |
15 |
16 | if __name__ == "__main__":
17 | # run unittest directly
18 | # this works only in a created test virtualenv, see:
19 | # http://code.google.com/p/django-sync-server/wiki/CreateTestEnvironment
20 | import os
21 | os.environ["DJANGO_SETTINGS_MODULE"] = "testproject.settings"
22 |
23 |
24 | from django.core.exceptions import ValidationError
25 | from django.contrib.auth.models import User
26 | from django.core.urlresolvers import reverse
27 | from django.test.client import Client
28 | from django.test import TestCase
29 | from django.conf import settings
30 |
31 | #from django_tools.utils import info_print
32 | #info_print.redirect_stdout()
33 |
34 | from weave.models import Wbo, Collection
35 | from weave.views import sync
36 | from weave import Logging
37 | from weave.utils import make_sync_hash
38 |
39 |
40 | def _enable_logging():
41 | logger = Logging.get_logger()
42 | handler = logging.StreamHandler()
43 | logger.addHandler(handler)
44 |
45 |
46 | class WeaveServerTest(TestCase):
47 | def setUp(self, *args, **kwargs):
48 | super(WeaveServerTest, self).setUp(*args, **kwargs)
49 |
50 | settings.WEAVE.DISABLE_LOGIN = False
51 |
52 | # Create a test user with basic auth data
53 | self.testuser = User(username="testuser")
54 | raw_password = "test user password!"
55 | self.testuser.set_password(raw_password)
56 | self.testuser.save()
57 |
58 | raw_auth_data = "%s:%s" % (self.testuser.username, raw_password)
59 | self.auth_data = "basic %s" % base64.b64encode(raw_auth_data)
60 |
61 | self.client = Client()
62 |
63 | def tearDown(self, *args, **kwargs):
64 | super(WeaveServerTest, self).tearDown(*args, **kwargs)
65 | self.testuser.delete()
66 |
67 | def assertWeaveTimestamp(self, response):
68 | """ Check if a valid weave timestamp is in response. """
69 | key = "x-weave-timestamp"
70 | try:
71 | raw_timestamp = response[key]
72 | except KeyError, err:
73 | self.fail("Weave timestamp (%s) not in response." % key)
74 |
75 | timestamp = float(raw_timestamp)
76 | comparison_value = time.time() - 1
77 | self.failUnless(timestamp > comparison_value,
78 | "Weave timestamp %r is not bigger than comparison value %r" % (timestamp, comparison_value)
79 | )
80 |
81 | def test_register_check_user_not_exist(self):
82 | """ test user.register_check view with not existing user. """
83 | url = reverse("weave-register_check", kwargs={"username":"user doesn't exist"})
84 | response = self.client.get(url)
85 | self.failUnlessEqual(response.content, "1")
86 |
87 | def test_register_check_user_exist(self):
88 | """ test user.register_check view with existing test user. """
89 | url = reverse("weave-register_check", kwargs={"username":self.testuser.username})
90 | response = self.client.get(url)
91 | self.failUnlessEqual(response.content, "0")
92 |
93 | def test_exists_with_not_existing_user(self):
94 | """ test user.exists view with not existing user. """
95 | url = reverse("weave-exists", kwargs={"username":"user doesn't exist", "version":"1.0"})
96 | response = self.client.get(url)
97 | self.failUnlessEqual(response.content, "0")
98 |
99 | def test_exists_with_existing_user(self):
100 | """ test user.exists view with existing test user. """
101 | url = reverse("weave-exists", kwargs={"username":self.testuser.username, "version":"1.0"})
102 | response = self.client.get(url)
103 | self.failUnlessEqual(response.content, "1")
104 |
105 | def test_basicauth_get_authenticate(self):
106 | """ test if we get 401 'unauthorized' response. """
107 | url = reverse("weave-info", kwargs={"username":self.testuser.username, "version":"1.0"})
108 | response = self.client.get(url)
109 | self.failUnlessEqual(response.status_code, 401) # Unauthorized: request requires user authentication
110 | self.failUnlessEqual(
111 | response["www-authenticate"], 'Basic realm="%s"' % settings.WEAVE.BASICAUTH_REALM
112 | )
113 | self.failUnlessEqual(response.content, "")
114 |
115 | def test_disable_basicauth(self):
116 | """ We should not get a basicauth response, if login is disabled. """
117 | settings.WEAVE.DISABLE_LOGIN = True
118 | url = reverse("weave-info", kwargs={"username":self.testuser.username, "version":"1.0"})
119 | response = self.client.get(url)
120 | self.failUnlessEqual(response.status_code, 403) # Forbidden
121 | self.failIf("www-authenticate" in response)
122 |
123 | def test_basicauth_send_authenticate(self):
124 | """ test if we can login via basicauth. """
125 | url = reverse("weave-info", kwargs={"username":self.testuser.username, "version":"1.1"})
126 | response = self.client.get(url, HTTP_AUTHORIZATION=self.auth_data)
127 | self.failUnlessEqual(response.status_code, 200)
128 | self.failUnlessEqual(response.content, "{}")
129 | self.failUnlessEqual(response["content-type"], "application/json")
130 | self.assertWeaveTimestamp(response)
131 |
132 | def test_create_wbo(self):
133 | url = reverse("weave-col_storage", kwargs={"username":"testuser", "version":"1.1", "col_name":"foobar"})
134 | data = (
135 | u'[{"id": "12345678-90AB-CDEF-1234-567890ABCDEF", "payload": "This is the payload"}]'
136 | )
137 | response = self.client.post(url, data=data, content_type="application/json", HTTP_AUTHORIZATION=self.auth_data)
138 | self.failUnlessEqual(response.content, u'{"failed": [], "success": ["12345678-90AB-CDEF-1234-567890ABCDEF"]}')
139 | self.failUnlessEqual(response["content-type"], "application/json")
140 |
141 | def test_wbo_ttl_out_of_range1(self):
142 | self.assertRaises(
143 | ValidationError,
144 | Wbo.objects.create,
145 | user=self.testuser, wboid="1", payload="", payload_size=0, ttl= -1,
146 | )
147 | def test_wbo_ttl_out_of_range2(self):
148 | self.assertRaises(
149 | ValidationError,
150 | Wbo.objects.create,
151 | user=self.testuser, wboid="1", payload="", payload_size=0, ttl=31536001,
152 | )
153 |
154 | def test_post_wbo_ttl_out_of_range1(self):
155 | url = reverse(sync.storage, kwargs={"username":"testuser", "version":"1.1", "col_name":"foobar"})
156 | data = (
157 | u'[{"id": "1", "payload": "This is the payload", "ttl": -1}]'
158 | )
159 | response = self.client.post(url, data=data, content_type="application/json", HTTP_AUTHORIZATION=self.auth_data)
160 | self.failUnlessEqual(response.content, u'{"failed": ["1"], "success": []}')
161 | self.failUnlessEqual(response["content-type"], "application/json")
162 |
163 | def test_post_wbo_ttl_out_of_range2(self):
164 | url = reverse(sync.storage, kwargs={"username":"testuser", "version":"1.1", "col_name":"foobar"})
165 | # settings.DEBUG = True
166 | # url += "?debug=1"
167 | data = (
168 | u'[{"id": "1", "payload": "This is the payload", "ttl": 31536001}]'
169 | )
170 | response = self.client.post(url, data=data, content_type="application/json", HTTP_AUTHORIZATION=self.auth_data)
171 | self.failUnlessEqual(response.content, u'{"failed": ["1"], "success": []}')
172 | self.failUnlessEqual(response["content-type"], "application/json")
173 |
174 | def test_csrf_exempt(self):
175 | url = reverse("weave-col_storage", kwargs={"username":"testuser", "version":"1.1", "col_name":"foobar"})
176 | data = (
177 | u'[{"id": "12345678-90AB-CDEF-1234-567890ABCDEF", "payload": "This is the payload"}]'
178 | )
179 | csrf_client = Client(enforce_csrf_checks=True)
180 |
181 | response = csrf_client.post(url, data=data, content_type="application/json", HTTP_AUTHORIZATION=self.auth_data)
182 |
183 | self.failUnlessEqual(response.content, u'{"failed": [], "success": ["12345678-90AB-CDEF-1234-567890ABCDEF"]}')
184 | self.failUnlessEqual(response["content-type"], "application/json")
185 |
186 | # Check if the csrf_exempt adds the csrf_exempt attribute to response:
187 | self.failUnlessEqual(response.csrf_exempt, True)
188 |
189 | def test_delete_not_existing_wbo(self):
190 | """
191 | http://github.com/jedie/django-sync-server/issues#issue/6
192 | """
193 | url = reverse("weave-wbo_storage",
194 | kwargs={
195 | "username":self.testuser.username, "version":"1.1",
196 | "col_name": "foobar", "wboid": "doesn't exist",
197 | }
198 | )
199 | response = self.client.delete(url, HTTP_AUTHORIZATION=self.auth_data)
200 | self.assertContains(response, "Page not found (404)", count=None, status_code=404)
201 | self.failUnlessEqual(response["content-type"], "text/html; charset=utf-8")
202 |
203 | # from django_tools.unittest_utils.BrowserDebug import debug_response
204 | # debug_response(response)
205 |
206 | class WeaveServerUserTest(TestCase):
207 | def test_create_user(self):
208 | # _enable_logging()
209 | email = u"test@test.tld"
210 | sync_hash = make_sync_hash(email)
211 | url = reverse("weave-exists", kwargs={"username":sync_hash, "version":"1.0"})
212 |
213 | data = u'{"password": "12345678", "email": "%s", "captcha-challenge": null, "captcha-response": null}' % email
214 |
215 | # Bug in django? The post data doesn't transfered to view, if self.client.put() used
216 | # response = self.client.put(url, data=data, content_type="application/json")
217 |
218 | # But this works:
219 | response = self.client.post(url, data=data, content_type="application/json",
220 | REQUEST_METHOD="PUT"
221 | )
222 |
223 | self.failUnlessEqual(response.content, u"")
224 |
225 | # Check if user was created:
226 | user = User.objects.get(username=sync_hash)
227 | self.failUnlessEqual(user.email, email)
228 |
229 |
230 | #__test__ = {"doctest": """
231 | #Another way to test that 1 + 1 is equal to 2.
232 | #
233 | #>>> 1 + 1 == 2
234 | #True
235 | #"""}
236 |
237 | if __name__ == "__main__":
238 | # Run all unittest directly
239 | from django.core import management
240 |
241 | tests = "weave"
242 | # tests = "weave.WeaveServerTest.test_create_user"
243 | # tests = "weave.WeaveServerTest.test_csrf_exempt"
244 | # tests = "weave.WeaveServerTest.test_post_wbo_ttl_out_of_range2"
245 |
246 | management.call_command('test', tests, verbosity=1)
247 |
--------------------------------------------------------------------------------
/weave/urls.py:
--------------------------------------------------------------------------------
1 | '''
2 | URL mapping.
3 |
4 | Created on 15.03.2010
5 |
6 | @license: GNU GPL v3 or above, see LICENSE for more details.
7 | @copyright: 2010 see AUTHORS for more details.
8 | @author: Jens Diemer
9 | @author: FladischerMichael
10 | '''
11 |
12 | from django.conf.urls.defaults import patterns, url
13 |
14 | from weave.views import sync, user, misc
15 |
16 | urlpatterns = patterns('weave',
17 | url(r'^(?P[\d\.]+?)/(?P.*?)/storage/(?P.*?)/(?P.*?)$', sync.storage, name="weave-wbo_storage"),
18 | url(r'^(?P[\d\.]+?)/(?P.*?)/storage/(?P.*?)$', sync.storage, name="weave-col_storage"),
19 | url(r'^(?P[\d\.]+?)/(?P.*?)/storage$', sync.storage),
20 | url(r'^(?P[\d\.]+?)/(?P.*?)/info/collections[/]?$', sync.info, name="weave-info"),
21 | url(r'^misc/(?P[\d\.]+?)/captcha_html$', misc.captcha, name="weave-captcha"),
22 | url(r'^user/(?P[\d\.]+?)/(?P.*?)/node/weave$', user.node, name="weave-node"),
23 | url(r'^user/(?P[\d\.]+?)/(?P.*?)/password$', user.password, name="weave-password"),
24 | url(r'^user/(?P[\d\.]+?)/(?P.*?)$', user.exists, name="weave-exists"),
25 | url(r'^api/register/check/(?P.*?)$', user.register_check, name="weave-register_check"),
26 | url(r'^weave-password-reset$', user.password_reset, name="weave-password_reset"),
27 | url(r'^$', misc.info_page, name="weave-info_page"),
28 | )
29 |
--------------------------------------------------------------------------------
/weave/utils.py:
--------------------------------------------------------------------------------
1 | # coding:utf-8
2 |
3 | '''
4 | Utility functions for Weave API.
5 |
6 | Created on 15.03.2010
7 |
8 | @license: GNU GPL v3 or above, see LICENSE for more details.
9 | @copyright: 2010-2011 see AUTHORS for more details.
10 | @author: Jens Diemer
11 | @author: FladischerMichael
12 | '''
13 |
14 | from datetime import datetime
15 | import base64
16 | import hashlib
17 | import time
18 |
19 | from weave import Logging
20 |
21 | logger = Logging.get_logger()
22 |
23 |
24 | def weave_timestamp(timedata=None):
25 | if timedata is None:
26 | timedata = datetime.now()
27 | return time.mktime(timedata.timetuple())
28 |
29 |
30 | def limit_wbo_queryset(request, queryset):
31 | """
32 | TODO:
33 | predecessorid = fromform(form, "predecessorid")
34 | full = fromform(form, "full")
35 | """
36 | GET = request.GET
37 |
38 | ids = GET.get("ids", None)
39 | if ids is not None:
40 | ids = ids.split(",")
41 | queryset = queryset.filter(wboid__in=ids)
42 |
43 | parentid = GET.get("parentid", None)
44 | if parentid is not None:
45 | queryset = queryset.filter(parentid=parentid)
46 |
47 | newer = GET.get("newer", None)
48 | if newer is not None: # Greater than or equal to newer modified timestamp
49 | queryset = queryset.filter(modified__gte=datetime.fromtimestamp(float(newer)))
50 |
51 | older = GET.get("older", None)
52 | if older is not None: # Less than or equal to older modified timestamp
53 | queryset = queryset.filter(modified__lte=datetime.fromtimestamp(float(older)))
54 |
55 | index_above = GET.get("index_above", None)
56 | if index_above is not None: # Greater than or equal to index_above modified timestamp
57 | queryset = queryset.filter(sortindex__gte=int(index_above))
58 |
59 | index_below = GET.get("index_below", None)
60 | if index_below is not None: # Less than or equal to index_below modified timestamp
61 | queryset = queryset.filter(sortindex__lte=int(index_below))
62 |
63 | sort_type = GET.get("sort", None)
64 | if sort_type is not None:
65 | if sort_type == 'oldest':
66 | queryset = queryset.order_by("modified")
67 | elif sort_type == 'newest':
68 | queryset = queryset.order_by("-modified")
69 | elif sort_type == 'index':
70 | queryset = queryset.order_by("wboid")
71 | else:
72 | raise NameError("sort type %r unknown" % sort_type)
73 |
74 | offset = GET.get("offset", None)
75 | if offset is not None:
76 | queryset = queryset[int(offset):]
77 |
78 | limit = GET.get("limit", None)
79 | if limit is not None:
80 | queryset = queryset[:int(limit)]
81 |
82 | return queryset
83 |
84 |
85 | def make_sync_hash(txt):
86 | """
87 | make a base32 encoded SHA1 hash value.
88 | Used in firefox sync for creating a username from the email address.
89 | See also:
90 | https://hg.mozilla.org/services/minimal-server/file/5ee9d9a4570a/weave_minimal/create_user#l87
91 | """
92 | sha1 = hashlib.sha1(txt).digest()
93 | base32encode = base64.b32encode(sha1).lower()
94 | return base32encode
95 |
--------------------------------------------------------------------------------
/weave/views/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/discontinue/django-sync-server/afe98dca07f08e36280143a2be9b8c4bad11fb81/weave/views/__init__.py
--------------------------------------------------------------------------------
/weave/views/misc.py:
--------------------------------------------------------------------------------
1 | # coding:utf-8
2 |
3 | '''
4 | misc views
5 | ~~~~~~~~~~
6 |
7 | Created on 27.03.2010
8 |
9 | Due to Mozilla Weave supporting Recaptcha solely, we have to stick with it until
10 | they decide to change the interface to pluggable captchas.
11 |
12 | @license: GNU GPL v3 or above, see LICENSE for more details.
13 | @copyleft: 2010-2012 by the django-sync-server team, see AUTHORS for more details.
14 | '''
15 |
16 | import time
17 |
18 | from django.conf import settings
19 | from django.core.exceptions import ImproperlyConfigured
20 | from django.http import HttpResponse
21 | from django.shortcuts import render_to_response
22 | from django.template import RequestContext
23 | from django.views.decorators.csrf import csrf_exempt
24 |
25 | # django-sync-server own stuff
26 | from weave import Logging, VERSION_STRING
27 | from weave.decorators import weave_assert_version, debug_sync_request
28 | from weave.models import Wbo
29 |
30 |
31 | logger = Logging.get_logger()
32 |
33 |
34 | @debug_sync_request
35 | @weave_assert_version(("1.0", "1.1"))
36 | @csrf_exempt
37 | def captcha(request, version):
38 | if settings.WEAVE.DONT_USE_CAPTCHA == True:
39 | logger.warn("You should activate captcha!")#
40 | return HttpResponse("Captcha support is disabled.")
41 |
42 | # Check for aviability of recaptcha
43 | # (can be found at: http://pypi.python.org/pypi/recaptcha-client)
44 | try:
45 | from recaptcha.client.captcha import displayhtml
46 | except ImportError:
47 | logger.error("Captcha requested but unable to import the 'recaptcha' package!")
48 | return HttpResponse("Captcha support disabled due to missing Python package 'recaptcha'.")
49 | if not getattr(settings.WEAVE, "RECAPTCHA_PUBLIC_KEY"):
50 | logger.error("Trying to create user but settings.WEAVE.RECAPTCHA_PUBLIC_KEY not set")
51 | raise ImproperlyConfigured
52 | # Send a simple HTML to the client. It get's rendered inside the Weave client.
53 | return HttpResponse("%s" % displayhtml(settings.WEAVE.RECAPTCHA_PUBLIC_KEY))
54 |
55 |
56 | def info_page(request):
57 | server_url = request.build_absolute_uri(request.path)
58 | if not server_url.endswith("/"):
59 | # sync setup dialog only accept the server url if it's ends with a slash
60 | server_url += "/"
61 |
62 | context = {
63 | "title": "django-sync-server - info page",
64 | "request": request,
65 | "weave_version": VERSION_STRING,
66 | "server_url":server_url,
67 | }
68 |
69 | if request.user.is_authenticated() and request.user.is_active:
70 | start_time = time.time()
71 |
72 | payload_queryset = Wbo.objects.filter(user=request.user.id).only("payload")
73 |
74 | wbo_count = 0
75 | payload_size = 0
76 | for item in payload_queryset.iterator():
77 | wbo_count += 1
78 | payload_size += len(item.payload)
79 |
80 | modified_queryset = Wbo.objects.filter(user=request.user.id).only("modified")
81 | try:
82 | latest = modified_queryset.latest("modified").modified
83 | except Wbo.DoesNotExist:
84 | # User hasn't used sync, so no WBOs exist from him
85 | latest = None
86 | oldest = None
87 | else:
88 | oldest = modified_queryset.order_by("modified")[0].modified
89 |
90 | duration = time.time() - start_time
91 |
92 | context.update({
93 | "wbo_count": wbo_count,
94 | "payload_size": payload_size,
95 | "duration": duration,
96 | "latest_modified": latest,
97 | "oldest_modified": oldest,
98 | })
99 |
100 | return render_to_response("weave/info_page.html", context, context_instance=RequestContext(request))
101 |
--------------------------------------------------------------------------------
/weave/views/sync.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | '''
4 | Storage for Weave.
5 |
6 | Created on 15.03.2010
7 |
8 | @license: GNU GPL v3 or above, see LICENSE for more details.
9 | @copyleft: 2010-2013 by the django-sync-server team, see AUTHORS for more details.
10 | '''
11 |
12 | from datetime import datetime
13 |
14 | try:
15 | import json # New in Python v2.6
16 | except ImportError:
17 | from django.utils import simplejson as json
18 |
19 | from django.conf import settings
20 | from django.views.decorators.csrf import csrf_exempt
21 | from django.core.exceptions import ValidationError
22 | from django.http import Http404
23 | from django.shortcuts import get_object_or_404
24 |
25 | # django-sync-server own stuff
26 | from weave.models import Collection, Wbo
27 | from weave.utils import limit_wbo_queryset, weave_timestamp
28 | from weave.decorators import weave_assert_username, weave_assert_version, \
29 | logged_in_or_basicauth, weave_render_response, debug_sync_request
30 | from weave import Logging
31 |
32 | logger = Logging.get_logger()
33 |
34 |
35 | @debug_sync_request
36 | @logged_in_or_basicauth
37 | @weave_assert_version(("1.0", "1.1"))
38 | @weave_assert_username
39 | @csrf_exempt
40 | @weave_render_response
41 | def info(request, version, username, timestamp):
42 | """
43 | return all collection keys with the timestamp
44 | https://wiki.mozilla.org/Labs/Weave/Sync/1.0/API#GET
45 | """
46 | collections = {}
47 | for collection in Collection.on_site.filter(user=request.user):
48 | collections[collection.name] = weave_timestamp(collection.modified)
49 | return collections
50 |
51 |
52 | @debug_sync_request
53 | @logged_in_or_basicauth
54 | @weave_assert_version(("1.0", "1.1"))
55 | @weave_assert_username
56 | @csrf_exempt
57 | @weave_render_response
58 | def storage(request, version, username, timestamp, col_name=None, wboid=None):
59 | """
60 | Handle storing Collections and WBOs.
61 | """
62 | if 'X-If-Unmodified-Since' in request.META:
63 | since = datetime.fromtimestamp(float(request.META['X-If-Unmodified-Since']))
64 | else:
65 | since = None
66 |
67 | if request.method == 'GET':
68 | # Returns a list of the WBO contained in a collection.
69 | collection = get_object_or_404(Collection.on_site, user=request.user, name=col_name)
70 |
71 | if wboid is not None: # return one WBO
72 | wbo = get_object_or_404(Wbo, user=request.user, collection=collection, wboid=wboid)
73 | return wbo.get_response_dict()
74 |
75 | wbo_queryset = Wbo.objects.filter(user=request.user, collection=collection)
76 | wbo_queryset = limit_wbo_queryset(request, wbo_queryset)
77 |
78 | # If defined, returns the full WBO, rather than just the id.
79 | if not 'full' in request.GET: # return only the WBO ids
80 | return [wbo.wboid for wbo in wbo_queryset]
81 |
82 | return [wbo.get_response_dict() for wbo in wbo_queryset]
83 |
84 | elif request.method == 'PUT':
85 | # https://wiki.mozilla.org/Labs/Weave/Sync/1.0/API#PUT
86 | # Adds the WBO defined in the request body to the collection.
87 |
88 | payload = request.raw_post_data
89 | logger.debug("Payload for PUT: %s" % payload)
90 | if not payload:
91 | raise NotImplementedError
92 |
93 | val = json.loads(payload)
94 |
95 | if val.get('id', None) != wboid:
96 | raise ValidationError
97 |
98 | # TODO: I don't think that it's good to just pass 0 in case the header is not defined
99 | collection, created = Collection.on_site.create_or_update(
100 | request.user,
101 | col_name,
102 | timestamp,
103 | since,
104 | )
105 |
106 | wbo, created = Wbo.objects.create_or_update(val, collection, request.user, timestamp)
107 |
108 | return weave_timestamp(timestamp)
109 |
110 | elif request.method == 'POST':
111 | status = {'success': [], 'failed': []}
112 | payload = request.raw_post_data
113 | logger.debug("Payload in POST: %s" % payload)
114 | if not payload:
115 | raise NotImplementedError
116 |
117 | # TODO: I don't think that it's good to just pass 0 in case the header is not defined
118 | collection, created = Collection.on_site.create_or_update(
119 | request.user,
120 | col_name,
121 | timestamp,
122 | since,
123 | )
124 |
125 | if created:
126 | logger.debug("Create new collection %s" % collection)
127 | else:
128 | logger.debug("Found existing collection %s" % collection)
129 |
130 | data = json.loads(payload)
131 |
132 | if not isinstance(data, list):
133 | raise NotImplementedError
134 |
135 | for item in data:
136 | try:
137 | wbo, created = Wbo.objects.create_or_update(item, collection, request.user, timestamp)
138 | except ValidationError, err:
139 | logger.debug("Can't create Wbo from %r: %s" % (item, err))
140 | status['failed'].append(item["id"])
141 | else:
142 | status['success'].append(wbo.wboid)
143 |
144 | if created:
145 | logger.debug("New wbo created: %s" % wbo)
146 | else:
147 | logger.debug("Existing wbo updated: %s" % wbo)
148 |
149 | return status
150 | elif request.method == 'DELETE':
151 | # FIXME: This is am mess, it needs better structure
152 | if col_name is None and wboid is None:
153 | Collection.on_site.filter(user=request.user).delete()
154 | return weave_timestamp(timestamp)
155 |
156 | if col_name is not None and wboid is not None:
157 | try:
158 | wbo = Wbo.objects.get(user=request.user, collection__name=col_name, wboid=wboid)
159 | except Wbo.DoesNotExist, err:
160 | msg = (
161 | "Deletion of wboid %s from collection %r for user %s requested,"
162 | " but there is no such wbo: %s"
163 | ) % (wboid, col_name, request.user, err)
164 | logger.info(msg)
165 | if settings.DEBUG:
166 | raise Http404(msg)
167 | else:
168 | raise Http404()
169 | else:
170 | logger.info("Delete Wbo %s in collection %s for user %s" % (wbo.wboid, col_name, request.user))
171 | wbo.delete()
172 | else:
173 | ids = request.GET.get('ids', None)
174 | if ids is not None:
175 | for wbo in Wbo.objects.filter(user=request.user, wboid__in=ids.split(',')):
176 | logger.info("Delete Wbo %s in collection %s for user %s" % (wbo.wboid, col_name, request.user))
177 | wbo.delete()
178 | else:
179 | collection = Collection.on_site.filter(user=request.user, name=col_name).delete()
180 | if collection is not None:
181 | logger.info("Delete collection %s for user %s" % (collection.name, request.user))
182 | Wbo.objects.filter(user=request.user, collection=collection).delete()
183 | else:
184 | logger.info("Deletion of collection %s requested but there is no such collection!" % (col_name))
185 | return weave_timestamp(timestamp)
186 |
187 | else:
188 | raise NotImplementedError("request.method %r not implemented!" % request.method)
189 |
--------------------------------------------------------------------------------
/weave/views/user.py:
--------------------------------------------------------------------------------
1 | # coding:utf-8
2 |
3 | '''
4 | User handling for Weave.
5 | FIXME: Not complete yet.
6 |
7 | Created on 15.03.2010
8 |
9 | @license: GNU GPL v3 or above, see LICENSE for more details.
10 | @copyleft: 2010-2011 by the django-sync-server team, see AUTHORS for more details.
11 | '''
12 |
13 | try:
14 | import json # New in Python v2.6
15 | except ImportError:
16 | from django.utils import simplejson as json
17 |
18 | from django.conf import settings
19 | from django.contrib.auth.models import User
20 | from django.core.exceptions import ImproperlyConfigured
21 | from django.core.urlresolvers import reverse
22 | from django.http import HttpResponseBadRequest, HttpResponse, \
23 | HttpResponseNotFound, HttpResponseRedirect
24 | from django.views.decorators.csrf import csrf_exempt
25 |
26 | # django-sync-server own stuff
27 | from weave import Logging
28 | from weave import constants
29 | from weave.decorators import logged_in_or_basicauth, weave_assert_version, debug_sync_request
30 | from weave.utils import make_sync_hash
31 |
32 | logger = Logging.get_logger()
33 |
34 |
35 | @debug_sync_request
36 | @weave_assert_version(("1.0", "1.1"))
37 | @logged_in_or_basicauth
38 | @csrf_exempt
39 | def password(request):
40 | """
41 | Change the user password.
42 | """
43 | if request.method != 'POST':
44 | logger.error("wrong request method %r" % request.method)
45 | return HttpResponseBadRequest()
46 |
47 | # Make sure that we are able to change the password.
48 | # If for example django-auth-ldap is used for authentication it will set the password for
49 | # User objects to a unusable one in the database. Therefore we cannot change it, it has to
50 | # happen inside LDAP.
51 | if not request.user.has_usable_password():
52 | logger.debug("Can't change password. User %s has a unusable password." % request.user.username)
53 | return HttpResponseBadRequest()
54 |
55 | # The PHP server for Weave uses the first 2048 (if there is enough data) characters
56 | # from POST data as the new password. We decided to throw an error if the password
57 | # data is longer than 256 characters.
58 | if len(request.raw_post_data) > 256:
59 | msg = (
60 | "Don't change password for user %s."
61 | " POST data has more than 256 characters! (len=%i)"
62 | ) % (request.user.username, len(request.raw_post_data))
63 | logger.debug(msg)
64 | return HttpResponseBadRequest()
65 |
66 | request.user.set_password(request.raw_post_data)
67 | request.user.save()
68 | logger.debug("Password for User %r changed to %r" % (request.user.username, request.raw_post_data))
69 | return HttpResponse()
70 |
71 |
72 | def password_reset(request):
73 | """
74 | Redirect to django own admin password change view.
75 | """
76 | return HttpResponseRedirect(reverse('admin:password_change'))
77 |
78 |
79 | @debug_sync_request
80 | @weave_assert_version(("1.0", "1.1"))
81 | @csrf_exempt
82 | def node(request, version, username):
83 | """
84 | finding cluster for user -> return 404 -> Using serverURL as data cluster (multi-cluster support disabled)
85 | """
86 | try:
87 | User.objects.get(username=username)
88 | except User.DoesNotExist:
89 | logger.debug("User %r doesn't exist!" % username)
90 | return HttpResponseNotFound(constants.ERR_UID_OR_EMAIL_AVAILABLE)
91 | else:
92 | logger.debug("User %r exist." % username)
93 | #FIXME: Send the actual cluster URL instead of 404
94 | return HttpResponseNotFound(constants.ERR_UID_OR_EMAIL_IN_USE)
95 |
96 |
97 | @debug_sync_request
98 | @csrf_exempt
99 | def register_check(request, username):
100 | """
101 | returns "1" if username is available (doesn't exist)
102 | https://wiki.mozilla.org/Labs/Weave/ServerAPI#Checking_if_Username.2FEmail_already_exists
103 | """
104 | try:
105 | User.objects.get(username=username)
106 | except User.DoesNotExist:
107 | logger.debug("User %r doesn't exist!" % username)
108 | return HttpResponse(constants.ERR_UID_OR_EMAIL_AVAILABLE)
109 | else:
110 | logger.debug("User %r exist." % username)
111 | return HttpResponse(constants.ERR_UID_OR_EMAIL_IN_USE)
112 |
113 |
114 | @weave_assert_version(("1.0", "1.1"))
115 | @csrf_exempt
116 | def exists(request, version, username):
117 | """
118 | https://wiki.mozilla.org/Labs/Weave/User/1.0/API#GET
119 | Returns 1 if the username is in use, 0 if it is available.
120 |
121 | e.g.: https://auth.services.mozilla.com/user/1/UserName
122 | """
123 | if request.method == 'GET':
124 | try:
125 | User.objects.get(username=username)
126 | except User.DoesNotExist:
127 | logger.debug("User %r doesn't exist!" % username)
128 | return HttpResponse(constants.USER_DOES_NOT_EXIST)
129 | else:
130 | logger.debug("User %r exist." % username)
131 | return HttpResponse(constants.USER_EXIST)
132 | elif request.method == 'PUT':
133 | # Handle user creation.
134 | logger.debug("Raw post data: %r" % request.raw_post_data)
135 | data = json.loads(request.raw_post_data)
136 |
137 | if settings.WEAVE.DONT_USE_CAPTCHA == True:
138 | logger.warn("Create user without captcha. You should activate captcha!")
139 | else:
140 | # Check for aviability of recaptcha
141 | # (can be found at: http://pypi.python.org/pypi/recaptcha-client)
142 | try:
143 | from recaptcha.client.captcha import submit
144 | except ImportError:
145 | logger.error("Captcha requested but unable to import the 'recaptcha' package!")
146 | return HttpResponse("Captcha support disabled due to missing Python package 'recaptcha'.")
147 | if not getattr(settings.WEAVE, "RECAPTCHA_PRIVATE_KEY"):
148 | logger.error("Trying to create user but settings.WEAVE.RECAPTCHA_PRIVATE_KEY not set")
149 | raise ImproperlyConfigured
150 |
151 | result = submit(
152 | data['captcha-challenge'],
153 | data['captcha-response'],
154 | settings.WEAVE.RECAPTCHA_PRIVATE_KEY,
155 | request.META['REMOTE_ADDR']
156 | )
157 | if not result.is_valid:
158 | # Captcha failed.
159 | return HttpResponseBadRequest()
160 |
161 | if len(username) > 32 or len(data['password']) > 256:
162 | return HttpResponseBadRequest()
163 |
164 | email = data['email']
165 | sync_hash = make_sync_hash(email)
166 | if not sync_hash == username:
167 | msg = "Error in sync hash: %r != %r" % (sync_hash, username)
168 | logger.error(msg)
169 | return HttpResponseBadRequest(msg)
170 |
171 | if len(username) <= 30:
172 | # Cut the sync sha1 hash to 30 characters, because
173 | # usernames are limited in django to a length of max. 30 chars.
174 | # http://docs.djangoproject.com/en/dev/topics/auth/#django.contrib.auth.models.User.username
175 | username = username[:30]
176 |
177 | try:
178 | user = User.objects.create_user(username, email, data['password'])
179 | except Exception, err:
180 | logger.error("Can't create user: %s" % err)
181 | else:
182 | logger.info("User %r with email %r created" % (user, email))
183 | return HttpResponse()
184 | else:
185 | raise NotImplemented()
186 |
--------------------------------------------------------------------------------