def show_associate(self, request, openid=None): "Screen that offers to associate an OpenID with a user's account" if not request.user.is_authenticated(): return self.need_authenticated_user(request) try: next = signed.loads(request.REQUEST.get('next', ''), extra_salt=self.salt_next) except ValueError: next = '' return self.render( request, self.show_associate_template, { 'action': urljoin(request.path, '../associate/'), 'user': request.user, 'specific_openid': openid, 'next': next and request.REQUEST.get('next', '') or None, 'openid_token': signed.dumps( # Use user.id as part of extra_salt to prevent attackers from # creating their own openid_token for use in CSRF attack openid, extra_salt=self.associate_salt + str(request.user.id)), })
def test_decode_detects_tampering(self): transforms = (lambda s: s.upper(), lambda s: s + "a", lambda s: "a" + s[1:], lambda s: s.replace(".", "")) value = {"foo": "bar", "baz": 1} encoded = signed.dumps(value) self.assertEqual(value, signed.loads(encoded)) for transform in transforms: self.assertRaises(signed.BadSignature, signed.loads, transform(encoded))
def get_user_session(self, request): try: user_session = signed.loads( request.COOKIES.get(self.cookie_user_session_key, '')) except ValueError: user_session = {} return user_session
def redirect_if_valid_next(self, request): "Logic for checking if a signed ?next= token is included in request" try: next = signed.loads(request.REQUEST.get('next', ''), extra_salt=self.salt_next) return HttpResponseRedirect(next) except ValueError: return None
def do_debug(self, request): if not settings.DEBUG: raise Http404 if self.cookie_key in request.COOKIES: obj = signed.loads(request.COOKIES[self.cookie_key], extra_salt=self.extra_salt) assert False, (obj, obj.__dict__) assert False, 'no cookie named %s' % self.cookie_key
def get_user_session(self, request): try: user_session = signed.loads( request.COOKIES.get(self.cookie_user_session_key, '') ) except ValueError: user_session = {} return user_session
def extract_incomplete_orequest(self, request): # Incomplete orequests are stashed in a cookie try: return signed.loads(request.COOKIES.get( self.incomplete_orequest_cookie_key, '' ), extra_salt = self.orequest_salt) except signed.BadSignature: return None
def testLogin(self): "Simulate a successful login" request, response = self.login() self.assert_('openid' in response.cookies, 'openid cookie not set') self.assertEqual(response['Location'], '/') # Decrypt the cookie and check it's the right thing cookie = response.cookies['openid'].value openid = signed.loads(cookie) self.assertEqual(openid.openid, 'http://simonwillison.net/')
def get_user_session(self, request): # By default Consumer uses Django sessions; here we over-ride so it # works using signed cookies instead. try: user_session = signed.loads( request.COOKIES.get(self.cookie_user_session_key, '')) except ValueError: user_session = {} return user_session
def selected(request): "Should only ever be POSTed to with a list of photo IDs in the 'photo'" photos_to_add = [] for photo in request.POST.getlist('photo'): try: photos_to_add.append(signed.loads(photo)) except ValueError: continue # Skip any that don't pass the signature check # Moderation is currently disabled: # is_visible = ( # request.user.get_profile().is_not_brand_new_account() or \ # request.user.is_staff # ) is_visible = True added_ids = [] set_id = None for photo in photos_to_add: if Photo.objects.filter(flickr_id = photo['id']).count(): continue p = Photo.objects.create( created_by = request.user, created_at = datetime.datetime.now(), title = photo['title'], photo = '', flickr_id = photo['id'], flickr_secret = photo['secret'], flickr_server = photo['server'], width_max_500 = int(photo['width_m']), height_max_500 = int(photo['height_m']), is_visible = is_visible, taken_at = parser.parse(photo['taken_at']), ) added_ids.append(p.id) # Should we add it to a set as well? if 'set_id' in photo: flickr_set, created = FlickrSet.objects.get_or_create( flickr_id = photo['set_id'], defaults = { 'title': photo['set_title'], 'description': photo['set_description'], 'user': request.user, } ) flickr_set.photos.add(p) set_id = photo['set_id'] url = '/%s/photos/unassigned/' % request.user.username if set_id: url = '/%s/photos/unassigned/by-flickr-set/%s/' % ( request.user.username, set_id ) if added_ids: url += '?ids=' + (','.join(map(str, added_ids))) return HttpResponseRedirect(url)
def testLoginBegin(self): "Can log in with an OpenID" openid_consumer = MyConsumer() post = rf.post('/openid/', {'openid_url': 'http://simonwillison.net/'}) post.session = MockSession() response = openid_consumer(post) self.assertEqual(response['Location'], 'http://url-of-openid-server/') oid_session = signed.loads(response.cookies['o_user_session'].value) self.assert_('openid_bits' in oid_session)
def testLogin(self): "Simulate a successful login" request, response = self.login() self.assert_('openid' in response.cookies, 'openid cookie not set') self.assertEqual(response['Location'], '/') # Decrypt the cookie and check it's the right thing cookie = response.cookies['openid'].value openid = signed.loads(cookie, extra_key=MyCookieConsumer().extra_salt) self.assertEqual(openid.openid, 'http://simonwillison.net/')
def redirect_if_valid_next(self, request): "Logic for checking if a signed ?next= token is included in request" try: next = signed.loads( request.REQUEST.get('next', ''), extra_salt=self.salt_next ) return HttpResponseRedirect(next) except ValueError: return None
def do_debug(self, request): if not settings.DEBUG: raise Http404 if self.cookie_key in request.COOKIES: obj = signed.loads( request.COOKIES[self.cookie_key], extra_salt = self.extra_salt ) assert False, (obj, obj.__dict__) assert False, 'no cookie named %s' % self.cookie_key
def get_user_session(self, request): # By default Consumer uses Django sessions; here we over-ride so it # works using signed cookies instead. try: user_session = signed.loads( request.COOKIES.get(self.cookie_user_session_key, '') ) except ValueError: user_session = {} return user_session
def test_encode_decode(self): objects = ( ('a', 'tuple'), 'a string', u'a unicode string \u2019', {'a': 'dictionary'}, ) for o in objects: self.assert_(o != signed.dumps(o)) self.assertEqual(o, signed.loads(signed.dumps(o)))
def testLoginBegin(self): "Can log in with an OpenID" openid_consumer = MyConsumer() post = rf.post('/openid/', { 'openid_url': 'http://simonwillison.net/' }) post.session = MockSession() response = openid_consumer(post) self.assertEqual(response['Location'], 'http://url-of-openid-server/') oid_session = signed.loads(response.cookies['o_user_session'].value) self.assert_('openid_bits' in oid_session)
def test_encode_decode(self): objects = ( ('a', 'tuple'), 'a string', u'a unicode string \u2019', { 'a': 'dictionary' }, ) for o in objects: self.assert_(o != signed.dumps(o)) self.assertEqual(o, signed.loads(signed.dumps(o)))
def process_request(self, request): self._cookie_needs_deleting = False request.openid = None request.openids = [] cookie_value = request.COOKIES.get(self.cookie_key, '') if cookie_value: try: request.openid = signed.loads(cookie_value, extra_salt=self.extra_salt) request.openids = [request.openid] except ValueError: # Signature failed self._cookie_needs_deleting = True
def show_login(self, request, message=None): try: next = signed.loads( request.REQUEST.get('next', ''), extra_salt=self.salt_next ) except ValueError: next = '' return self.render(request, self.login_template, { 'action': request.path, 'logo': self.logo_path or (request.path + 'logo/'), 'message': message, 'next': next and request.REQUEST.get('next', '') or None, })
def show_login(self, request, message=None): try: next = signed.loads(request.REQUEST.get('next', ''), extra_salt=self.salt_next) except ValueError: next = '' return self.render( request, self.login_template, { 'action': request.path, 'logo': self.logo_path or (request.path + 'logo/'), 'message': message, 'next': next and request.REQUEST.get('next', '') or None, })
def process_request(self, request): self._cookie_needs_deleting = False request.openid = None request.openids = [] cookie_value = request.COOKIES.get(self.cookie_key, '') if cookie_value: try: request.openid = signed.loads( cookie_value, extra_key = self.extra_salt ) request.openids = [request.openid] except ValueError: # Signature failed self._cookie_needs_deleting = True
def test_decode_detects_tampering(self): transforms = ( lambda s: s.upper(), lambda s: s + 'a', lambda s: 'a' + s[1:], lambda s: s.replace('.', ''), ) value = {'foo': 'bar', 'baz': 1} encoded = signed.dumps(value) self.assertEqual(value, signed.loads(encoded)) for transform in transforms: self.assertRaises(signed.BadSignature, signed.loads, transform(encoded))
def do_associate(self, request): if request.method == 'POST': try: openid = signed.loads(request.POST.get('openid_token', ''), extra_key=self.associate_salt + str(request.user.id)) except signed.BadSignature: return self.show_error(request, self.csrf_failed_message) # Associate openid with their account, if it isn't already if not request.user.openids.filter(openid=openid): request.user.openids.create(openid=openid) return self.show_associate_done(request, openid) return self.show_error(request, 'Should POST to here')
def test_decode_detects_tampering(self): transforms = ( lambda s: s.upper(), lambda s: s + 'a', lambda s: 'a' + s[1:], lambda s: s.replace('.', ''), ) value = {'foo': 'bar', 'baz': 1} encoded = signed.dumps(value) self.assertEqual(value, signed.loads(encoded)) for transform in transforms: self.assertRaises( signed.BadSignature, signed.loads, transform(encoded) )
def process_decide(self, request): try: orequest = signed.loads(request.POST.get("orequest", ""), self.secret_key) except ValueError: return self.show_error(request, self.invalid_decide_post_message) they_said_yes = bool(("yes_once" in request.POST) or ("yes_always" in request.POST)) if "yes_always" in request.POST: self.save_trusted_root(request, orequest.identity, orequest.trust_root) # TODO: Double check what we should be passing as identity= here: oresponse = orequest.answer(they_said_yes, identity=orequest.identity) self.add_sreg_data(request, orequest, oresponse) return self.server_response(request, oresponse)
def redirect_if_valid_next(self, request): "Logic for checking if a signed ?next= token is included in request" if self.sign_next_param: try: next = signed.loads(request.REQUEST.get('next', ''), extra_key=self.salt_next) return HttpResponseRedirect(next) except ValueError: return None else: next = request.REQUEST.get('next', '') if next.startswith('/'): return HttpResponseRedirect(next) else: return None
def do_associate(self, request): if request.method == 'POST': try: openid = signed.loads( request.POST.get('openid_token', ''), extra_salt = self.associate_salt + str(request.user.id) ) except signed.BadSignature: return self.show_error(request, self.csrf_failed_message) # Associate openid with their account, if it isn't already if not request.user.openids.filter(openid = openid): request.user.openids.create(openid = openid) return self.show_associate_done(request, openid) return self.show_error(request, 'Should POST to here')
def redirect_if_valid_next(self, request): "Logic for checking if a signed ?next= token is included in request" if self.sign_next_param: try: next = signed.loads( request.REQUEST.get('next', ''), extra_key=self.salt_next ) return HttpResponseRedirect(next) except ValueError: return None else: next = request.REQUEST.get('next', '') if next.startswith('/'): return HttpResponseRedirect(next) else: return None
def get_on_complete_url(self, request, on_complete_url=None): "Derives an appropriate on_complete_url from the request" on_complete_url = on_complete_url or self.on_complete_url or \ (request.path + 'complete/') on_complete_url = self.ensure_absolute_url(request, on_complete_url) try: next = signed.loads(request.POST.get('next', ''), extra_salt=self.salt_next) except ValueError: return on_complete_url if '?' not in on_complete_url: on_complete_url += '?next=' + self.sign_next(next) else: on_complete_url += '&next=' + self.sign_next(next) return on_complete_url
def do_associations(self, request): "Interface for managing your account's associated OpenIDs" if not request.user.is_authenticated(): return self.need_authenticated_user(request) message = None if request.method == 'POST': if 'todelete' in request.POST: # Something needs deleting; find out what try: todelete = signed.loads( request.POST['todelete'], extra_key=self.associate_delete_salt) if todelete['user_id'] != request.user.id: message = self.associate_tampering_message else: # It matches! Delete the OpenID relationship request.user.openids.filter( pk=todelete['association_id']).delete() message = self.association_deleted_message % ( todelete['openid']) except signed.BadSignature: message = self.associate_tampering_message # We construct a button to delete each existing association openids = [] for association in request.user.openids.all(): openids.append({ 'openid': association.openid, 'button': signed.dumps( { 'user_id': request.user.id, 'association_id': association.id, 'openid': association.openid, }, extra_key=self.associate_delete_salt), }) return self.render( request, self.associations_template, { 'openids': openids, 'user': request.user, 'action': request.path, 'logo': self.logo_path or (request.path + '../logo/'), 'message': message, 'action_new': '../', 'associate_next': self.sign_next(request.path), })
def process_decide(self, request): try: orequest = signed.loads(request.POST.get('orequest', ''), self.secret_key) except ValueError: return self.show_error(request, self.invalid_decide_post_message) they_said_yes = bool(('yes_once' in request.POST) or ('yes_always' in request.POST)) if 'yes_always' in request.POST: self.save_trusted_root(request, orequest.identity, orequest.trust_root) # TODO: Double check what we should be passing as identity= here: oresponse = orequest.answer(they_said_yes, identity=orequest.identity) self.add_sreg_data(request, orequest, oresponse) return self.server_response(request, oresponse)
def get_on_complete_url(self, request, on_complete_url=None): "Derives an appropriate on_complete_url from the request" on_complete_url = on_complete_url or self.on_complete_url or \ (request.path + 'complete/') on_complete_url = self.ensure_absolute_url(request, on_complete_url) try: next = signed.loads( request.POST.get('next', ''), extra_salt=self.salt_next ) except ValueError: return on_complete_url if '?' not in on_complete_url: on_complete_url += '?next=' + self.sign_done(next) else: on_complete_url += '&next=' + self.sign_done(next) return on_complete_url
def do_associations(self, request): "Interface for managing your account's associated OpenIDs" if not request.user.is_authenticated(): return self.need_authenticated_user(request) message = None if request.method == 'POST': if 'todelete' in request.POST: # Something needs deleting; find out what try: todelete = signed.loads( request.POST['todelete'], extra_key = self.associate_delete_salt ) if todelete['user_id'] != request.user.id: message = self.associate_tampering_message else: # It matches! Delete the OpenID relationship request.user.openids.filter( pk = todelete['association_id'] ).delete() message = self.association_deleted_message % ( todelete['openid'] ) except signed.BadSignature: message = self.associate_tampering_message # We construct a button to delete each existing association openids = [] for association in request.user.openids.all(): openids.append({ 'openid': association.openid, 'button': signed.dumps({ 'user_id': request.user.id, 'association_id': association.id, 'openid': association.openid, }, extra_key = self.associate_delete_salt), }) return self.render(request, self.associations_template, { 'openids': openids, 'user': request.user, 'action': request.path, 'logo': self.logo_path or (request.path + '../logo/'), 'message': message, 'action_new': '../', 'associate_next': self.sign_next(request.path), })
def show_associate(self, request, openid=None): "Screen that offers to associate an OpenID with a user's account" if not request.user.is_authenticated(): return self.need_authenticated_user(request) try: next = signed.loads( request.REQUEST.get('next', ''), extra_salt=self.salt_next ) except ValueError: next = '' return self.render(request, self.show_associate_template, { 'action': urljoin(request.path, '../associate/'), 'user': request.user, 'specific_openid': openid, 'next': next and request.REQUEST.get('next', '') or None, 'openid_token': signed.dumps( # Use user.id as part of extra_salt to prevent attackers from # creating their own openid_token for use in CSRF attack openid, extra_salt = self.associate_salt + str(request.user.id) ), })
def test_encode_decode(self): objects = (("a", "tuple"), "a string", u"a unicode string \u2019", {"a": "dictionary"}) for o in objects: self.assert_(o != signed.dumps(o)) self.assertEqual(o, signed.loads(signed.dumps(o)))
def process_submission(request): # Process the previous submission try: options = signed.loads(request.POST.get('options', '')) except ValueError: return {} if (int(time.time()) - options['time']) > (5 * 60): return {} # Form is too old if not utils.check_token(options['token']): return {} # Token invalid species_pk = options['species'] contestants = options['contestants'] winner = int(request.POST.get('winner', '')) if not winner: return {} loser = (set(contestants) - set([winner])).pop() # Record a win! context = utils.record_win(species_pk, winner, loser) if not request.user.is_anonymous(): utils.record_contribution_from(request.user.username) photos = Photo.objects.select_related( 'created_by' ).in_bulk([winner, loser]) last_species = Species.objects.get(pk = species_pk) description = ''' %s: <a href="%s">%s</a>: <a href="%s"><img src="%s"></a> beat <a href="%s"><img src="%s"></a> ''' % ( str(datetime.datetime.now()), last_species.get_absolute_url(), last_species.common_name, photos[winner].get_absolute_url(), photos[winner].thumb_75_url(), photos[loser].get_absolute_url(), photos[loser].thumb_75_url(), ) if not request.user.is_anonymous(): description += ' (rated by <a href="%s">%s</a>)' % ( request.user.username, request.user.username ) # And record the species so we don't show it to them multiple times set_key = utils.USER_SEEN_SET % request.user.username list_key = utils.USER_SEEN_LIST % request.user.username r.push(list_key, species_pk, head=True) r.sadd(set_key, species_pk) if r.scard(set_key) >= SEEN_SPECIES_COUNT: r.srem(set_key, r.pop(list_key, tail=True)) r.push('bestpic-activity', description, head=True) r.ltrim('bestpic-activity', 0, 200) context.update({ 'last_species': last_species, 'last_winner': photos[winner], 'last_loser': photos[loser], 'show_link_to_best': utils.species_has_top_10(last_species), }) return context