Adding signing (and signed cookies) to Django core

2009-09-24 Thread Simon Willison

As mentioned in the thread about cookie-based notifications, at the
DjangoCon Sprints I raised the subject of adding signing (and signed
cookies) to Django core.

I've found myself using signing more and more over time, and I think
it's a concept which is common enough to deserve inclusion in Django -
if anything, its use should be actively encouraged by the framework.

It's also something that's hard to do correctly. At the sprints Armin
pointed out that I should be using hmac, not straight sha1, for
generating signatures (something Django itself gets wrong in the few
places that implement signing already). Having a cryptographer-
approved implementation will save a lot of people from making the same
mistakes.

Signed cookies
==

On top of signing (which I imagine would live in django.utils) I'd
like to add a signed cookie implementation. Signed cookies are useful
for all sorts of things - most importantly, they can be used in place
of sessions in many places, which improves performance (and overall
scalability) by removing the need to access a persistent session
backend on every hit. Set the user's username in a signed cookie and
you can display "Logged in as X" messages on every page without any
persistence layer calls at all.

I think signed cookies should either be a separate API from
response.set_cookie or should be provided as an additional argument to
that method. I'm not a fan of signing using middleware (as seen in
http://code.google.com/p/django-signedcookies/ ) since that approach
signs everything - some cookies, such as those used by Google
Analytics, need to remain unsigned.

So the API could either be:

response.set_signed_cookie(key, value)

Or...

response.set_cookie(key, value, signed=True)

(I prefer the latter option)

Proposed signing implementation
===

I'd be happy to donate my signing code from django-openid to the
cause, which was written to be usable entirely separately from the
rest of the django-openid codebase:

http://github.com/simonw/django-openid/blob/master/django_openid/signed.py
http://github.com/simonw/django-openid/blob/master/django_openid/tests/signing_tests.py

This offers two APIs: sign/unsign and dumps/loads. sign and unsign
generate and append signatures to bytestrings and confirm that they
have not been tampered with. dumps and loads can be used to create
signed pickles of arbitrary Python objects.

Here's what the API would look like with this library:

>>> from django.utils import signed
>>> signed.sign('hello')
'hello.9asVJn9dfv6qLJ_BYObzF7mmH8c'

The signature is a URL-safe base64 encoded digest of the hmac/sha1. I
used base64 rather than .hexdigest() for space reasons - base64
digests are 27 characters, hexadecimal digests are 40. When you're
including signatures in cookies and URLs (especially account recovery
URLs sent out in plain text, 80 character wide e-mails) every byte
counts.

>>> signed.unsign('hello.9asVJn9dfv6qLJ_BYObzF7mmH8c')
'hello'
>>> signed.unsign('hello.badsignature')
Traceback (most recent call last):
...
BadSignature: Signature failed: badsignature

BadSignature is a subclass of ValueError, meaning lazy developers
(like myself) can do the following rather than importing the exception
itself:

try:
value = signed.unsign(signed_value)
except ValueError:
return tamper_error_view(request)

>>> signed.dumps({"a": "foo"})
'KGRwMApTJ2EnCnAxClMnZm9vJwpwMgpzLg.mYepoYkzWwXRmsCTVJm3Mb0HHz4'
>>> signed.loads(_)
{'a': 'foo'}

Again, the pickle is URL-safe base64 encoded to take up less valuable
cookie space and generally make it easier to pass around on the Web. A
nice thing about URL-safe base64 is that it uses 64 out of the 65 URL-
safe characters (by URL-safe I mean characters that are left unchanged
by Python's urllib.urlencode function) - the remaining character is
the period, which I use to separate the pickle from the signature.

signed.dumps takes a couple of extra optional arguments. The first is
compress=True (default is False) which zlib compresses the pickle if
doing so will save any space:

>>> import this # to get an object worth compressing
...
>>> len(signed.dumps(this.s))
1207
>>> len(signed.dumps(this.s, compress=True))
637

By default, all signatures use Django's SECRET_KEY. If you want to
sign with a different key, you can pass it as an argument to the
various functions:

>>> signed.sign('hello', key='sekrit')
'hello.o6MKehoOfZ2b2FU84wzibW6IWxI'
>>> signed.unsign(_, key='sekrit')
'hello'

The dumps and loads methods also take a key argument, as well as an
additional optional extra_key argument for if you want to generate
different signatures for different parts of your application (useful
for the extra paranoid):

>>> signed.dumps('hello', extra_key='ultra')
'UydoZWxsbycKcDAKLg.1XYDpILo5xqSwImfa3WuJJT4RPo'
>>> signed.loads(_, extra_key='ultra')
'hello'

We'd want to get a proper cryptographer to give this the once-over
before adding it to core, but I'm generally happy with 

Re: Adding signing (and signed cookies) to Django core

2009-09-24 Thread Tobias McNulty
+1 for signed cookies.  Your API looks reasonable and I'd agree that
set_cookie(..., signed=True) fits better with the rest of the API as well.
What about some sanity checking to make sure that, if SECRET_KEY is used, it
is, at the very least, a non-empty string?

On Thu, Sep 24, 2009 at 1:18 PM, Simon Willison wrote:

>
> As mentioned in the thread about cookie-based notifications, at the
> DjangoCon Sprints I raised the subject of adding signing (and signed
> cookies) to Django core.
>
> I've found myself using signing more and more over time, and I think
> it's a concept which is common enough to deserve inclusion in Django -
> if anything, its use should be actively encouraged by the framework.
>
> It's also something that's hard to do correctly. At the sprints Armin
> pointed out that I should be using hmac, not straight sha1, for
> generating signatures (something Django itself gets wrong in the few
> places that implement signing already). Having a cryptographer-
> approved implementation will save a lot of people from making the same
> mistakes.
>
> Signed cookies
> ==
>
> On top of signing (which I imagine would live in django.utils) I'd
> like to add a signed cookie implementation. Signed cookies are useful
> for all sorts of things - most importantly, they can be used in place
> of sessions in many places, which improves performance (and overall
> scalability) by removing the need to access a persistent session
> backend on every hit. Set the user's username in a signed cookie and
> you can display "Logged in as X" messages on every page without any
> persistence layer calls at all.
>
> I think signed cookies should either be a separate API from
> response.set_cookie or should be provided as an additional argument to
> that method. I'm not a fan of signing using middleware (as seen in
> http://code.google.com/p/django-signedcookies/ ) since that approach
> signs everything - some cookies, such as those used by Google
> Analytics, need to remain unsigned.
>
> So the API could either be:
>
>response.set_signed_cookie(key, value)
>
> Or...
>
>response.set_cookie(key, value, signed=True)
>
> (I prefer the latter option)
>
> Proposed signing implementation
> ===
>
> I'd be happy to donate my signing code from django-openid to the
> cause, which was written to be usable entirely separately from the
> rest of the django-openid codebase:
>
> http://github.com/simonw/django-openid/blob/master/django_openid/signed.py
>
> http://github.com/simonw/django-openid/blob/master/django_openid/tests/signing_tests.py
>
> This offers two APIs: sign/unsign and dumps/loads. sign and unsign
> generate and append signatures to bytestrings and confirm that they
> have not been tampered with. dumps and loads can be used to create
> signed pickles of arbitrary Python objects.
>
> Here's what the API would look like with this library:
>
> >>> from django.utils import signed
> >>> signed.sign('hello')
> 'hello.9asVJn9dfv6qLJ_BYObzF7mmH8c'
>
> The signature is a URL-safe base64 encoded digest of the hmac/sha1. I
> used base64 rather than .hexdigest() for space reasons - base64
> digests are 27 characters, hexadecimal digests are 40. When you're
> including signatures in cookies and URLs (especially account recovery
> URLs sent out in plain text, 80 character wide e-mails) every byte
> counts.
>
> >>> signed.unsign('hello.9asVJn9dfv6qLJ_BYObzF7mmH8c')
> 'hello'
> >>> signed.unsign('hello.badsignature')
> Traceback (most recent call last):
> ...
> BadSignature: Signature failed: badsignature
>
> BadSignature is a subclass of ValueError, meaning lazy developers
> (like myself) can do the following rather than importing the exception
> itself:
>
> try:
>value = signed.unsign(signed_value)
> except ValueError:
>return tamper_error_view(request)
>
> >>> signed.dumps({"a": "foo"})
> 'KGRwMApTJ2EnCnAxClMnZm9vJwpwMgpzLg.mYepoYkzWwXRmsCTVJm3Mb0HHz4'
> >>> signed.loads(_)
> {'a': 'foo'}
>
> Again, the pickle is URL-safe base64 encoded to take up less valuable
> cookie space and generally make it easier to pass around on the Web. A
> nice thing about URL-safe base64 is that it uses 64 out of the 65 URL-
> safe characters (by URL-safe I mean characters that are left unchanged
> by Python's urllib.urlencode function) - the remaining character is
> the period, which I use to separate the pickle from the signature.
>
> signed.dumps takes a couple of extra optional arguments. The first is
> compress=True (default is False) which zlib compresses the pickle if
> doing so will save any space:
>
> >>> import this # to get an object worth compressing
> ...
> >>> len(signed.dumps(this.s))
> 1207
> >>> len(signed.dumps(this.s, compress=True))
> 637
>
> By default, all signatures use Django's SECRET_KEY. If you want to
> sign with a different key, you can pass it as an argument to the
> various functions:
>
> >>> signed.sign('hello', key='sekrit')
> 'hello.o6MKehoOfZ2b2FU84wzibW6IWxI'
> >>> sign

Re: Adding signing (and signed cookies) to Django core

2009-09-24 Thread Marty Alchin

I'm obviously going to weigh in here, having authored the signed
cookies app you mentioned below, but be aware I'm not a cryptographer,
nor do I have any personal use for signed cookies at all. I can
appreciate their value, but I'm not even using my own app in anything,
so if there are problems with it, I haven't experienced them
first-hand. I won't comment on everything, because I certainly trust
your thoughts on the matter. (Your comment on the Django book is what
prompted me to write the app in the first place!) I do have a couple
thoughts, though. So, anything I don't comment on, I agree with.

On Thu, Sep 24, 2009 at 1:18 PM, Simon Willison  wrote:
> It's also something that's hard to do correctly. At the sprints Armin
> pointed out that I should be using hmac, not straight sha1, for
> generating signatures (something Django itself gets wrong in the few
> places that implement signing already). Having a cryptographer-
> approved implementation will save a lot of people from making the same
> mistakes.

I have no idea how hmac differs from straight sha1, but this was
raised in a django-signedcookies issue as well, and i've since
integrated it. I'm with you on this; if somebody who knows better
recommends something, I'm inclined to listen.

> I think signed cookies should either be a separate API from
> response.set_cookie or should be provided as an additional argument to
> that method. I'm not a fan of signing using middleware (as seen in
> http://code.google.com/p/django-signedcookies/ ) since that approach
> signs everything - some cookies, such as those used by Google
> Analytics, need to remain unsigned.

I admit, I hadn't considered third-party cookies when I put it
together as a decorator. Client-side cookie access is likely
problematic as well, but that'll always be questionable anyway. You
can't validate a cookie in the client without divulging your secret
key and you can't just ignore the signature, because that defeats the
whole purpose. My app also provides a decorator, which might help in
some rare situations, but most of the time things like analytics
cookies will be in a base template and will always be included in
whatever view happens to have the decorator. I'm very surprised that
in all this time, nobody submitted a bug about the analytics problem.

> So the API could either be:
>
>    response.set_signed_cookie(key, value)
>
> Or...
>
>    response.set_cookie(key, value, signed=True)
>
> (I prefer the latter option)

I prefer the latter as well, for an added reason. I'd personally like
to invest some time in seeing if there are any other interesting
pitfalls in set_cookie() based on it deferring to Python's
SimpleCookie implementation. When writing Pro Django, I realized that
SimpleCookie expects everything to be strings, so #6657 came up with
secure=False resulting in a secure cookie after all. I don't know if
there are other such issues, but it might be worth looking at in
detail if we already have to add in signed cookie support.

And before anyone asks, no I don't think tying the signing behavior
into secure=True would be a good idea. Secure is meant to tell the
browser it should only send the cookie back over a secure channel,
such as HTTPS. While people who need secure cookies may also want
signed cookies, they're two separate ideas that I don't think would do
well mixed together. Of course, that leaves us with a potential
response.set_cookie(key, value, secure=True, signed=True), but I think
it's worth it to be explicit.

But I do have one other concern with either of these APIs that is at
least worth discussing: the disparity between setting a signed cookie
and retrieving one.

response.set_cookie(key, value, signed=True)
value = signed.unsign(request.COOKIES[key])

Mainly, unsigning a cookie requires importing and directly using the
signing module anyway, so the benefit of having set_cookie() handle it
transparently is fairly weak (unless, of course, I'm missing something
obvious). I'd rather just see the signing code made available and
well-documented, so that at least the change in code when adding
signed cookies is equivalent on both sides.

Another option would be to have request.COOKIES be a custom
dictionary, with an extra .get_unsigned(key) method that would work
like .get(), but validates the signature along the way. That behavior
can't be added straight to __getitem__() though, because that has no
way of knowing whether the cookie was signed to begin with.

These are the types of issues that led me to just implement it as a
middleware, so the API could be as dead simple as possible. With these
new issues in mind, I don't think dead simplicity is really an option,
so I'd rather fall back to being explicit.

 signed.unsign('hello.9asVJn9dfv6qLJ_BYObzF7mmH8c')
> 'hello'
 signed.unsign('hello.badsignature')
> Traceback (most recent call last):
> ...
> BadSignature: Signature failed: badsignature
>
> BadSignature is a subclass of ValueError, meaning lazy develop

Re: Adding signing (and signed cookies) to Django core

2009-09-24 Thread Simon Willison

On Sep 24, 7:37 pm, Marty Alchin  wrote:
> Another option would be to have request.COOKIES be a custom
> dictionary, with an extra .get_unsigned(key) method that would work
> like .get(), but validates the signature along the way. That behavior
> can't be added straight to __getitem__() though, because that has no
> way of knowing whether the cookie was signed to begin with.

Hmm... I hadn't considered that. I was thinking that the unsigning
could be transparent, so by the time you access request.COOKIES['key']
the value had already been unsigned (and if the signature failed the
cookie key just wouldn't be set at all, as if the cookie never
existed). But as you point out, this doesn't work because you can't
tell if the cookie was signed or not in the first place.

We could fix this with a naming convention of some sort:

response.set_cookie('key', 'value', sign=True)
- results in a Set-Cookie: key__Xsigned=value header

But that's pretty ugly. Not sure what to do about this one -
request.unsign_cookie('key') might be an option, as at least that
reflects the set_cookie / sign / unsign API a bit. Not ideal by a long
shot though.

> eyes. Yeah, I suppose we could perhaps make that a subclass of
> ValueError or TypeError, but I would worry about people wrapping it up
> into something else that might cause problems.
>
> try:
>     value = signed.unsign(signed_value).decode('utf-8')
> except ValueError:
>     # Whoops! UnicodeDecodeError winds up here as well!

That's a great argument against subclassing ValueError - I hadn't
considered the unicode case. You're right, if anything it should
subclass SuspiciousOperation instead.

Cheers,

Simon

--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
"Django developers" group.
To post to this group, send email to django-developers@googlegroups.com
To unsubscribe from this group, send email to 
django-developers+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/django-developers?hl=en
-~--~~~~--~~--~--~---



Re: Adding signing (and signed cookies) to Django core

2009-09-24 Thread Marty Alchin

On Thu, Sep 24, 2009 at 2:54 PM, Simon Willison  wrote:
> Hmm... I hadn't considered that. I was thinking that the unsigning
> could be transparent, so by the time you access request.COOKIES['key']
> the value had already been unsigned (and if the signature failed the
> cookie key just wouldn't be set at all, as if the cookie never
> existed). But as you point out, this doesn't work because you can't
> tell if the cookie was signed or not in the first place.

The behavior you mention here is exactly what django-signedcookies
does, but it can only do so because it can blindly assume that all
cookies are signed, which as you pointed out, causes other problems.

> We could fix this with a naming convention of some sort:
>
> response.set_cookie('key', 'value', sign=True)
> - results in a Set-Cookie: key__Xsigned=value header

That seems pretty ugly on the surface, but it does confine the
ugliness to somewhere most people won't bother to look. One potential
problem is if someone wants to use __Xsigned in the name of an
unsigned cookie, but a namespace clash like that should be extremely
rare in practice.

Also, does the name of a cookie factor into the cookie length limits?
My reading of RFC 2109 says yes, but it'd be worth verifying, since it
would cut down on the usable value space. With your compressed base64
stuff, that's not as big of a problem, but still something to look
into.

> request.unsign_cookie('key') might be an option, as at least that
> reflects the set_cookie / sign / unsign API a bit. Not ideal by a long
> shot though.

>> try:
>>     value = signed.unsign(signed_value).decode('utf-8')
>> except ValueError:
>>     # Whoops! UnicodeDecodeError winds up here as well!
>
> That's a great argument against subclassing ValueError - I hadn't
> considered the unicode case. You're right, if anything it should
> subclass SuspiciousOperation instead.

I don't know if it's completely anti-ValueError, because a ValueError
subclass does still make some sense semantically, and since you can
catch more than one exception type in a try block, it's perfectly
functional. It's just that when lazy people blindly catch ValueError
without checking for something more specific as well, things can
break.

So it really just comes down to whether we expect people to be
thorough or lazy. Hrm. Yeah, I guess that answers it. :)

-Gul

--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
"Django developers" group.
To post to this group, send email to django-developers@googlegroups.com
To unsubscribe from this group, send email to 
django-developers+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/django-developers?hl=en
-~--~~~~--~~--~--~---



Re: Adding signing (and signed cookies) to Django core

2009-09-24 Thread Benjamin Slavin

The lack of parallelism in terms of the interface is my biggest hangup here.

I do think that this should find it's way into trunk, as signed
cookies are important in the use cases you mention and are really easy
to get wrong... and getting it wrong can be dangerous.

I'm not going to get into the dumps/loads bit right now because
there's enough to tackle on signed cookies.


On Thu, Sep 24, 2009 at 2:54 PM, Simon Willison  wrote:
> Hmm... I hadn't considered that. I was thinking that the unsigning
> could be transparent, so by the time you access request.COOKIES['key']
> the value had already been unsigned (and if the signature failed the
> cookie key just wouldn't be set at all, as if the cookie never
> existed). But as you point out, this doesn't work because you can't
> tell if the cookie was signed or not in the first place.
>
> We could fix this with a naming convention of some sort:
>
> response.set_cookie('key', 'value', sign=True)
> - results in a Set-Cookie: key__Xsigned=value header

Unfortunately, this approach won't work.

A malicious client can just send "key" rather than "key__Xsigned" and
you'll never know that the cookie hasn't gone through validation.
This also means that you can't look for cookie values that match a
specific format (ex: body.signature) because a malicious client could
just drop the signature portion.

As always, we can't trust the client. :-(

This means that unsigning can *never* be fully transparent.  We need a
symmetric specification of the fact that a given cookie should,
indeed, be signed.

> But that's pretty ugly. Not sure what to do about this one -
> request.unsign_cookie('key') might be an option, as at least that
> reflects the set_cookie / sign / unsign API a bit. Not ideal by a long
> shot though.

I'm not sure what the best solution is, but here are some of the
options I've considered:

1) request.unsign_cookie('foo') -- This breaks the parallelism with
existing cookies.  Sometimes we'll be doing request.COOKIES['foo'] and
sometimes we'll be doing request.unsign_cookie('foo').

2) A decorator for views -- @unsign_cookies("foo", "bar") -- This
doesn't allow any sort of fall-back (you can't customize what to do if
a given cookie is improperly signed)

3) COOKIES as an intelligent object -- We can overload .get so we're
doing something like request.COOKIES.get('foo', signed=True) -- I
think this has the best chance at an interface that keeps a consistent
feel. It's completely backward compatible, though it breaks the
traditional expectation of what you can do via the `get` method on a
dictionary.


Best,
 - Ben

--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
"Django developers" group.
To post to this group, send email to django-developers@googlegroups.com
To unsubscribe from this group, send email to 
django-developers+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/django-developers?hl=en
-~--~~~~--~~--~--~---



Re: Adding signing (and signed cookies) to Django core

2009-09-24 Thread Marty Alchin

> Also, does the name of a cookie factor into the cookie length limits?
> My reading of RFC 2109 says yes, but it'd be worth verifying, since it
> would cut down on the usable value space. With your compressed base64
> stuff, that's not as big of a problem, but still something to look
> into.

Also, just to throw this out there for the sake of compleness: could
the signature be stored under a separate name, rather than being
bundled with the original cookie itself?

Set-Cookie: key=value
Set-Cookie: key__Xsignature=signature_string

It seems like this could address a couple issues at once.

* There's a clear distinction between signed and unsigned cookies, so
request.COOKIES can be populated with only valid cookies

* The key/value pair remains unchanged, so things like Google
Analytics can happily ignore the signature if it was applied
unnecessarily (middleware is back on the table!)

Since there may be an upper limit on the number of allowed cookies,
though, doubling the number of cookies could present some very real
problems. RFC 2109 recommends allowing at least 20 cookies per domain
name, and it looks like at least Microsoft takes that to be a
maximum,[1] so it could present very real problems (middleware is back
off the table!).

I'm not sure how many cookies people use on a regular basis, and this
would only be for explicitly signed cookies, so maybe it'd be okay,
but it's flirting dangerously close to being completely unworkable.
Worse yet, it doesn't look like there's any predictable way to know
which cookies would get lost in the event of having too many, so this
may end up causing some very weird application errors if things go
wrong.

At least now it's been recorded for future reference. (Hello, future
me, looking up information on why we did things the way we did! Do we
have flying cars yet?)

-Gul

[1] http://support.microsoft.com/kb/306070

--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
"Django developers" group.
To post to this group, send email to django-developers@googlegroups.com
To unsubscribe from this group, send email to 
django-developers+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/django-developers?hl=en
-~--~~~~--~~--~--~---



Re: Adding signing (and signed cookies) to Django core

2009-09-24 Thread Simon Willison

On Sep 24, 8:22 pm, Benjamin Slavin  wrote:
> Unfortunately, this approach won't work.
>
> A malicious client can just send "key" rather than "key__Xsigned" and
> you'll never know that the cookie hasn't gone through validation.
> This also means that you can't look for cookie values that match a
> specific format (ex: body.signature) because a malicious client could
> just drop the signature portion.
>
> As always, we can't trust the client. :-(

Good point - that rules that approach out.

> 1) request.unsign_cookie('foo') -- This breaks the parallelism with
> existing cookies.  Sometimes we'll be doing request.COOKIES['foo'] and
> sometimes we'll be doing request.unsign_cookie('foo').

If we were going to do that, it would make sense to NOT have set_cookie
(... sign=True) as the API for setting one. We could achieve
parallelism with something like this:

response.sign_cookie('key', 'value')
...
value = request.unsign_cookie('key')

You can still read request.COOKIES directly, but you'll get the raw,
signed value. That API doesn't look too ugly to me.

> 2) A decorator for views -- @unsign_cookies("foo", "bar") -- This
> doesn't allow any sort of fall-back (you can't customize what to do if
> a given cookie is improperly signed)

If a cookie is improperly signed I think you silently discard it, as
if it was never set. If we had logging this could always be logged as
well... we could fire a signal if we really think people might want to
further customise it.

> 3) COOKIES as an intelligent object -- We can overload .get so we're
> doing something like request.COOKIES.get('foo', signed=True) -- I
> think this has the best chance at an interface that keeps a consistent
> feel. It's completely backward compatible, though it breaks the
> traditional expectation of what you can do via the `get` method on a
> dictionary.

This isn't so bad, since we already have a precedent for this in
request.POST.get_list('foo'). request.COOKIES.get_signed(key) might be
OK.

At the moment I think my preference is for response.sign_cookie and
request.unsign_cookie, though I'm a bit worried that "unsign cookie"
doesn't obviously mean "get the cookie, check if the signature is OK
and return the value if it is". I like that unsign_cookie maps to the
low level signed.unsign API, but it might well be that most users
never use the low level signing API and the cookie stuff is the only
bit of it they ever see.

Cheers,

Simon

--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
"Django developers" group.
To post to this group, send email to django-developers@googlegroups.com
To unsubscribe from this group, send email to 
django-developers+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/django-developers?hl=en
-~--~~~~--~~--~--~---



Re: Adding signing (and signed cookies) to Django core

2009-09-24 Thread Marty Alchin

On Thu, Sep 24, 2009 at 3:22 PM, Benjamin Slavin
 wrote:
>> response.set_cookie('key', 'value', sign=True)
>> - results in a Set-Cookie: key__Xsigned=value header
>
> Unfortunately, this approach won't work.
>
> A malicious client can just send "key" rather than "key__Xsigned" and
> you'll never know that the cookie hasn't gone through validation.
> This also means that you can't look for cookie values that match a
> specific format (ex: body.signature) because a malicious client could
> just drop the signature portion.
>
> As always, we can't trust the client. :-(

And you've just added another reason my followup email is invalid,
though I sent that before reading your response. (Take that, future
me!)

> 3) COOKIES as an intelligent object -- We can overload .get so we're
> doing something like request.COOKIES.get('foo', signed=True) -- I
> think this has the best chance at an interface that keeps a consistent
> feel. It's completely backward compatible, though it breaks the
> traditional expectation of what you can do via the `get` method on a
> dictionary.

I was wondering about this option as well, after I mentioned adding a
request.COOKIES.get_unsigned() method. I actually like this idea a
lot, personally. As you mention, it's backward compatible, and I'm not
sure it completely breaks the traditional expectation. After all,
aren't subclasses expected to customize the behavior of their parents?
It doesn't change any existing behavior, but rather just adds
something extra.

The one downside to using get() directly, as opposed to an altogether
new method, is that get() doesn't raise a KeyError when a value
doesn't exist. That means if anyone's wrapping request.COOKIES[key] in
a try block and catching KeyError, changing to the new code is more
than just a one-liner. I'm personally okay with this, but it's
definitely worth noting.

-Gul

--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
"Django developers" group.
To post to this group, send email to django-developers@googlegroups.com
To unsubscribe from this group, send email to 
django-developers+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/django-developers?hl=en
-~--~~~~--~~--~--~---



Re: Adding signing (and signed cookies) to Django core

2009-09-24 Thread Johannes Dollinger

How about Signer class?

signer = Signer(key=...)
assert signer.unsign(signer.sign(value)) == value

This way you wouldn't have to pass around key, extra_key, and  
potential further arguments but a single Signer instance. Plus, you  
could easyly overwrite hashing, concatenation, and serialization as  
well as add functionality transparently, eg.:

sig = TimestampedSignatureFactory(ttl=3600) # sig.unsign() will raise  
SigntureExpired after 3600 seconds

> 1) request.unsign_cookie('foo') -- This breaks the parallelism with
> existing cookies.  Sometimes we'll be doing request.COOKIES['foo'] and
> sometimes we'll be doing request.unsign_cookie('foo').
>
> 2) A decorator for views -- @unsign_cookies("foo", "bar") -- This
> doesn't allow any sort of fall-back (you can't customize what to do if
> a given cookie is improperly signed)
>
> 3) COOKIES as an intelligent object -- We can overload .get so we're
> doing something like request.COOKIES.get('foo', signed=True) -- I
> think this has the best chance at an interface that keeps a consistent
> feel. It's completely backward compatible, though it breaks the
> traditional expectation of what you can do via the `get` method on a
> dictionary.

4) A signed.Cookies object:

signed_cookies = signed.Cookies(request, key=...)
signed_cookies.set_cookie(key, value, **kwargs)
value = signed_cookies[key]

or

signed_cookies = signer.get_cookies(request)
...

__
Johannes





--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
"Django developers" group.
To post to this group, send email to django-developers@googlegroups.com
To unsubscribe from this group, send email to 
django-developers+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/django-developers?hl=en
-~--~~~~--~~--~--~---



Re: Adding signing (and signed cookies) to Django core

2009-09-24 Thread Chris Beaven

+1 on the concept of a signing module.

On Sep 25, 7:48 am, Marty Alchin  wrote:

> The one downside to using get() directly, as opposed to an altogether
> new method, is that get() doesn't raise a KeyError when a value
> doesn't exist. That means if anyone's wrapping request.COOKIES[key] in
> a try block and catching KeyError, changing to the new code is more
> than just a one-liner.

Adding my coat of paint to the shed...

Rather than a "request.unsign_cookie" method, provide a
"request.SIGNED_COOKIES" property (or perhaps alteratively,
request.COOKIES.signed if the interface was useful enough to use
across request.GET/POST too) containing a lazy dict-like object which
only retrieves (correctly) signed cookies.
This way you are using a similar interface, but it's obvious in code
that you're only interested in signed ones.

For example:

# raises KeyError
value = request.SIGNED_COOKIES['bad-cookie']

# value == None
value = request.SIGNED_COOKIES.get('bad-cookie')

Personally, I don't see much point in specifically reporting on
incorrectly signed cookies - imo they should just be treated as if
they never existed. If someone really cared, they can look in
request.COOKIES to see if the cookie was in there but not in
SIGNED_COOKIES.
--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
"Django developers" group.
To post to this group, send email to django-developers@googlegroups.com
To unsubscribe from this group, send email to 
django-developers+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/django-developers?hl=en
-~--~~~~--~~--~--~---



Re: Adding signing (and signed cookies) to Django core

2009-09-24 Thread Ian Lewis

On Fri, Sep 25, 2009 at 4:46 AM, Simon Willison  wrote:
> This isn't so bad, since we already have a precedent for this in
> request.POST.get_list('foo'). request.COOKIES.get_signed(key) might be
> OK.

request.COOKIES.get_signed(key) makes the most sense to me since it's
clear you are dealing with cookies and as you said it looks something
like the request.POST object. Though it's a bit more verbose than
request.unsign_cookie(key), accessing cookies directly through the
request object looks wierd and unsign_cookie could be ambiguous.

Ian

--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
"Django developers" group.
To post to this group, send email to django-developers@googlegroups.com
To unsubscribe from this group, send email to 
django-developers+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/django-developers?hl=en
-~--~~~~--~~--~--~---



Re: Adding signing (and signed cookies) to Django core

2009-09-24 Thread Ian Lewis

On Fri, Sep 25, 2009 at 6:33 AM, Chris Beaven  wrote:
>
> +1 on the concept of a signing module.
>
> On Sep 25, 7:48 am, Marty Alchin  wrote:
>
>> The one downside to using get() directly, as opposed to an altogether
>> new method, is that get() doesn't raise a KeyError when a value
>> doesn't exist. That means if anyone's wrapping request.COOKIES[key] in
>> a try block and catching KeyError, changing to the new code is more
>> than just a one-liner.
>
> Adding my coat of paint to the shed...
>
> Rather than a "request.unsign_cookie" method, provide a
> "request.SIGNED_COOKIES" property (or perhaps alteratively,
> request.COOKIES.signed if the interface was useful enough to use
> across request.GET/POST too) containing a lazy dict-like object which
> only retrieves (correctly) signed cookies.
> This way you are using a similar interface, but it's obvious in code
> that you're only interested in signed ones.
>
> For example:
>
> # raises KeyError
> value = request.SIGNED_COOKIES['bad-cookie']
>
> # value == None
> value = request.SIGNED_COOKIES.get('bad-cookie')
>
> Personally, I don't see much point in specifically reporting on
> incorrectly signed cookies - imo they should just be treated as if
> they never existed. If someone really cared, they can look in
> request.COOKIES to see if the cookie was in there but not in
> SIGNED_COOKIES.

The problem is that you don't know which cookies are signed and which
aren't for the reasons posted earlier. So you don't know which cookies
to put in SIGNED_COOKIES and which to put in COOKIES unless accessing
COOKIES gives you raw values of ALL cookies and SIGNED_COOKIES
attempts to unsign ALL cookies. That seems really clunky.

You have to sign and unsign the cookies yourself in code which makes
the sign_cookie/unsign_cookie API make sense.

Ian

--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
"Django developers" group.
To post to this group, send email to django-developers@googlegroups.com
To unsubscribe from this group, send email to 
django-developers+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/django-developers?hl=en
-~--~~~~--~~--~--~---



Re: Adding signing (and signed cookies) to Django core

2009-09-24 Thread Chris Beaven

On Sep 25, 1:56 pm, Ian Lewis  wrote:
> [...] unless accessing
> COOKIES gives you raw values of ALL cookies and SIGNED_COOKIES
> attempts to unsign ALL cookies. That seems really clunky.

Yes, all cookies would stay in COOKIES.
SIGNED_COOKIES would be a lazy dict-like object, not a plain
dictionary. It'd only unsign all cookies if you iterated its keys or
values.

But I just realised it doesn't cover the case for using a different
signing key (or extra signing data though).

I think I now prefer Simon's request.COOKIES.get_signed(key) method
(which could handle different/extra signing data).
I really don't like the term "unsigning" (and therefore don't like the
method name unsign_cookie) - it doesn't explain what is happening
clearly.
--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
"Django developers" group.
To post to this group, send email to django-developers@googlegroups.com
To unsubscribe from this group, send email to 
django-developers+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/django-developers?hl=en
-~--~~~~--~~--~--~---



How to customize field to get the correct python value?

2009-09-24 Thread Jay

Hi there,

I have a question on customize the db field. For this field, I want to
save integers in db, but want them to be my custom class when they are
used in Python code. In Django docs, it's said that I should set the
__metaclass__ to be models.SubfieldBase to make to_python() work. I
tried it and succeeded when all data of one db row are fetched.

But I then found that if I use query method like values(), values_list
() or only(), the data of my custom field will not be converted into
my desired type but the raw db integer. Then I tried Django's own
DateField, it works OK also in such scenario. So I think there must be
something I missed.

Could you help on this question? I cannot find more materials to move
on, so I'm very eager to get your help. Many thanks!

--
http://j-lite.net

--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
"Django developers" group.
To post to this group, send email to django-developers@googlegroups.com
To unsubscribe from this group, send email to 
django-developers+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/django-developers?hl=en
-~--~~~~--~~--~--~---



Re: How to customize field to get the correct python value?

2009-09-24 Thread Russell Keith-Magee

On Fri, Sep 25, 2009 at 10:40 AM, Jay  wrote:
>
> Hi there,
>
> I have a question on customize the db field. For this field, I want to
> save integers in db, but want them to be my custom class when they are
> used in Python code. In Django docs, it's said that I should set the
> __metaclass__ to be models.SubfieldBase to make to_python() work. I
> tried it and succeeded when all data of one db row are fetched.
>
> But I then found that if I use query method like values(), values_list
> () or only(), the data of my custom field will not be converted into
> my desired type but the raw db integer. Then I tried Django's own
> DateField, it works OK also in such scenario. So I think there must be
> something I missed.
>
> Could you help on this question? I cannot find more materials to move
> on, so I'm very eager to get your help. Many thanks!

Two points:

1) Please don't cross post messages to multiple mailing lists. Find
the right forum, and ask once.

2) Django-developers is not the right place. This mailing list is for
discussing the development of Django itself, not answering general
user queries. Django-users is the list you want.

Yours,
Russ Magee %-)

--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
"Django developers" group.
To post to this group, send email to django-developers@googlegroups.com
To unsubscribe from this group, send email to 
django-developers+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/django-developers?hl=en
-~--~~~~--~~--~--~---



Re: Adding signing (and signed cookies) to Django core

2009-09-24 Thread Russell Keith-Magee

On Fri, Sep 25, 2009 at 1:18 AM, Simon Willison  wrote:
>
> As mentioned in the thread about cookie-based notifications, at the
> DjangoCon Sprints I raised the subject of adding signing (and signed
> cookies) to Django core.
>
> I've found myself using signing more and more over time, and I think
> it's a concept which is common enough to deserve inclusion in Django -
> if anything, its use should be actively encouraged by the framework.

Put me down as +1 in favor of adding support for signed cookies in some form.

As for the exact form that the API will take - I don't have any
particularly strong opinions at this point, and there are plenty of
big brains weighing in, so I will stay out of the discussion and let
the community evolve the idea.

By way of greasing the wheels towards trunk: if the outcome of this
mailing list thread was a wiki page that digested all the ideas,
concerns and issues into a single page, it will make the final
approval process much easier. Luke Plant's wiki page on the proposed
CSRF changes [1] is a good model to follow here - I wasn't involved in
the early stages of that discussion, but thanks to that wiki page, I
was able to come up to speed very quickly and see why certain ideas
were rejected.

[1] http://code.djangoproject.com/wiki/CsrfProtection

Yours,
Russ Magee %-)

--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
"Django developers" group.
To post to this group, send email to django-developers@googlegroups.com
To unsubscribe from this group, send email to 
django-developers+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/django-developers?hl=en
-~--~~~~--~~--~--~---



Re: Adding signing (and signed cookies) to Django core

2009-09-24 Thread Eric Florenzano

A big +1 on signed cookies, and I like the direction the discussion is
going.

Also, I hope this doesn't derail this discussion, but I hope after
signed cookies are added, auth can be made to optionally use signed
cookies instead of sessions.

Thanks,
Eric Florenzano
--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
"Django developers" group.
To post to this group, send email to django-developers@googlegroups.com
To unsubscribe from this group, send email to 
django-developers+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/django-developers?hl=en
-~--~~~~--~~--~--~---