From f05b2761189b32d45fd5d80598d2716f0ff1125d Mon Sep 17 00:00:00 2001 From: eric Date: Tue, 3 Mar 2015 17:39:23 -0500 Subject: [PATCH 01/19] provide a title that's kindle-safe --- core/models.py | 16 ++++++++++++++++ frontend/views.py | 5 ++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/core/models.py b/core/models.py index 68fc5ae4..9767375d 100755 --- a/core/models.py +++ b/core/models.py @@ -9,6 +9,7 @@ import random import urllib import urllib2 from urlparse import urlparse +import unicodedata from ckeditor.fields import RichTextField from datetime import timedelta, datetime @@ -1186,6 +1187,21 @@ class Work(models.Model): elif self.authors().count()>2: return "%s et al." % self.authors()[0].name return '' + + def kindle_safe_title(self): + safe = u'' + nkfd_form = unicodedata.normalize('NFKD', self.title) #unaccent accented letters + for c in nkfd_form: + ccat = unicodedata.category(c) + #print ccat + if ccat.startswith('L') or ccat.startswith('N'): # only letters and numbers + if ord(c) > 127: + safe = safe + '#' #a non latin script letter or number + else: + safe = safe + c + elif not unicodedata.combining(c): #not accents (combining forms) + safe = safe + '_' #punctuation + return safe def last_campaign(self): # stash away the last campaign to prevent repeated lookups diff --git a/frontend/views.py b/frontend/views.py index c503d64d..1b38c88b 100755 --- a/frontend/views.py +++ b/frontend/views.py @@ -3185,7 +3185,7 @@ def send_to_kindle(request, work_id, javascript='0'): if acq: ebook_url = acq.get_mobi_url() ebook_format = 'mobi' - title = acq.work.title + title = acq.work.kindle_safe_title() else: non_google_ebooks = work.ebooks().exclude(provider='Google Books') try: @@ -3201,8 +3201,7 @@ def send_to_kindle(request, work_id, javascript='0'): ebook_url = ebook.url ebook_format = ebook.format logger.info('ebook: {0}, user_ip: {1}'.format(work_id, request.META['REMOTE_ADDR'])) - title = ebook.edition.title - title = title.replace(' ', '_') + title = ebook.edition.work.kindle_safe_title() context['ebook_url']=ebook_url context['ebook_format']=ebook_format From 5c64cfac3869669cdc296333900d019894084223 Mon Sep 17 00:00:00 2001 From: Raymond Yee Date: Wed, 4 Mar 2015 10:53:54 -0800 Subject: [PATCH 02/19] testing SSL configuration to try to disallow RC4 and enable forward secrecy --- deploy/just.conf | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/deploy/just.conf b/deploy/just.conf index defb5798..9a9f257f 100644 --- a/deploy/just.conf +++ b/deploy/just.conf @@ -15,7 +15,11 @@ Redirect permanent / https://just.unglue.it/ SSLEngine on ServerName just.unglue.it:443 -SSLProtocol All -SSLv2 -SSLv3 + +SSLProtocol all -SSLv2 -SSLv3 +SSLHonorCipherOrder On +SSLCipherSuite EECDH+AES:EDH+AES:-SHA1:EECDH+RC4:EDH+RC4:RC4-SHA:EECDH+AES256:EDH+AES256:AES256-SHA:!aNULL:!eNULL:!EXP:!LOW:!MD5 + SSLCertificateFile /etc/ssl/certs/server.crt SSLCertificateKeyFile /etc/ssl/private/server.key SSLCertificateChainFile /etc/ssl/certs/STAR_unglue_it.ca-bundle From 9477ae66f2c92df834e379f19ffb2c0f9842b428 Mon Sep 17 00:00:00 2001 From: Raymond Yee Date: Wed, 4 Mar 2015 11:35:25 -0800 Subject: [PATCH 03/19] first config try didn't up our score....now trying config generated by mozilla --- deploy/just.conf | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/deploy/just.conf b/deploy/just.conf index 9a9f257f..5f353814 100644 --- a/deploy/just.conf +++ b/deploy/just.conf @@ -16,9 +16,16 @@ Redirect permanent / https://just.unglue.it/ SSLEngine on ServerName just.unglue.it:443 -SSLProtocol all -SSLv2 -SSLv3 -SSLHonorCipherOrder On -SSLCipherSuite EECDH+AES:EDH+AES:-SHA1:EECDH+RC4:EDH+RC4:RC4-SHA:EECDH+AES256:EDH+AES256:AES256-SHA:!aNULL:!eNULL:!EXP:!LOW:!MD5 +# generated using https://mozilla.github.io/server-side-tls/ssl-config-generator/ +# intermediate mode +# 2015.03.04 (with Apache v 2.2.22 and OpenSSL 1.0.1 and HSTS enabled) + +SSLProtocol all -SSLv2 -SSLv3 +SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA +SSLHonorCipherOrder on + +# HSTS (mod_headers is required) (15768000 seconds = 6 months) +Header always add Strict-Transport-Security "max-age=15768000" SSLCertificateFile /etc/ssl/certs/server.crt SSLCertificateKeyFile /etc/ssl/private/server.key From 25b8749206aa4fc88ca1437a091f9418f4833d97 Mon Sep 17 00:00:00 2001 From: Raymond Yee Date: Thu, 5 Mar 2015 12:07:30 -0800 Subject: [PATCH 04/19] Let's see what using the old configuration to be more compatible with old browsers does for the ssl test --- deploy/just.conf | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/deploy/just.conf b/deploy/just.conf index 5f353814..58f7e22c 100644 --- a/deploy/just.conf +++ b/deploy/just.conf @@ -17,12 +17,12 @@ SSLEngine on ServerName just.unglue.it:443 # generated using https://mozilla.github.io/server-side-tls/ssl-config-generator/ -# intermediate mode -# 2015.03.04 (with Apache v 2.2.22 and OpenSSL 1.0.1 and HSTS enabled) +# old mode (https://wiki.mozilla.org/Security/Server_Side_TLS#Old_backward_compatibility) +# 2015.03.05 (with Apache v 2.2.22 and OpenSSL 1.0.1 and HSTS enabled) -SSLProtocol all -SSLv2 -SSLv3 -SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA -SSLHonorCipherOrder on +SSLProtocol all -SSLv2 +SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA +SSLHonorCipherOrder on # HSTS (mod_headers is required) (15768000 seconds = 6 months) Header always add Strict-Transport-Security "max-age=15768000" From bcc1abed009a4350b5116e94050c71ea28b424c0 Mon Sep 17 00:00:00 2001 From: Raymond Yee Date: Thu, 5 Mar 2015 12:15:12 -0800 Subject: [PATCH 05/19] Now let's try https://community.qualys.com/blogs/securitylabs/2013/08/05/configuring-apache-nginx-and-openssl-for-forward-secrecy --- deploy/just.conf | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/deploy/just.conf b/deploy/just.conf index 58f7e22c..1dd881fa 100644 --- a/deploy/just.conf +++ b/deploy/just.conf @@ -16,13 +16,11 @@ Redirect permanent / https://just.unglue.it/ SSLEngine on ServerName just.unglue.it:443 -# generated using https://mozilla.github.io/server-side-tls/ssl-config-generator/ -# old mode (https://wiki.mozilla.org/Security/Server_Side_TLS#Old_backward_compatibility) -# 2015.03.05 (with Apache v 2.2.22 and OpenSSL 1.0.1 and HSTS enabled) +# https://community.qualys.com/blogs/securitylabs/2013/08/05/configuring-apache-nginx-and-openssl-for-forward-secrecy -SSLProtocol all -SSLv2 -SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA +SSLProtocol all -SSLv2 -SSLv3 SSLHonorCipherOrder on +SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS" # HSTS (mod_headers is required) (15768000 seconds = 6 months) Header always add Strict-Transport-Security "max-age=15768000" From c9a0fc8ee7741e260f201152d00534297431e65a Mon Sep 17 00:00:00 2001 From: Raymond Yee Date: Thu, 5 Mar 2015 12:24:20 -0800 Subject: [PATCH 06/19] tweak from the article SSLCipherSuite EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS +RC4 RC4 --- deploy/just.conf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deploy/just.conf b/deploy/just.conf index 1dd881fa..4a783af3 100644 --- a/deploy/just.conf +++ b/deploy/just.conf @@ -17,10 +17,11 @@ SSLEngine on ServerName just.unglue.it:443 # https://community.qualys.com/blogs/securitylabs/2013/08/05/configuring-apache-nginx-and-openssl-for-forward-secrecy +# deeper down the article SSLProtocol all -SSLv2 -SSLv3 SSLHonorCipherOrder on -SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS" +SSLCipherSuite SSLCipherSuite EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS +RC4 RC4 # HSTS (mod_headers is required) (15768000 seconds = 6 months) Header always add Strict-Transport-Security "max-age=15768000" From 2685940069ae746ae10d94decb8130c515bbc015 Mon Sep 17 00:00:00 2001 From: Raymond Yee Date: Thu, 5 Mar 2015 12:25:34 -0800 Subject: [PATCH 07/19] ooops typo --- deploy/just.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/just.conf b/deploy/just.conf index 4a783af3..57923857 100644 --- a/deploy/just.conf +++ b/deploy/just.conf @@ -21,7 +21,7 @@ ServerName just.unglue.it:443 SSLProtocol all -SSLv2 -SSLv3 SSLHonorCipherOrder on -SSLCipherSuite SSLCipherSuite EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS +RC4 RC4 +SSLCipherSuite EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS +RC4 RC4 # HSTS (mod_headers is required) (15768000 seconds = 6 months) Header always add Strict-Transport-Security "max-age=15768000" From 8506df2480d1deaddcf923f40fae2425fcc02ee8 Mon Sep 17 00:00:00 2001 From: Raymond Yee Date: Thu, 5 Mar 2015 12:26:40 -0800 Subject: [PATCH 08/19] need "" around ciphers --- deploy/just.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/just.conf b/deploy/just.conf index 57923857..e71c5340 100644 --- a/deploy/just.conf +++ b/deploy/just.conf @@ -21,7 +21,7 @@ ServerName just.unglue.it:443 SSLProtocol all -SSLv2 -SSLv3 SSLHonorCipherOrder on -SSLCipherSuite EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS +RC4 RC4 +SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS +RC4 RC4" # HSTS (mod_headers is required) (15768000 seconds = 6 months) Header always add Strict-Transport-Security "max-age=15768000" From 2e274b4e2b96ca37bf2d476d6ec38e96cc9ab202 Mon Sep 17 00:00:00 2001 From: Raymond Yee Date: Thu, 5 Mar 2015 12:30:47 -0800 Subject: [PATCH 09/19] config without RC4 ://community.qualys.com/blogs/securitylabs/2013/08/05/configuring-apache-nginx-and-openssl-for-forward-secrecy --- deploy/just.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/just.conf b/deploy/just.conf index e71c5340..3a448f20 100644 --- a/deploy/just.conf +++ b/deploy/just.conf @@ -17,11 +17,11 @@ SSLEngine on ServerName just.unglue.it:443 # https://community.qualys.com/blogs/securitylabs/2013/08/05/configuring-apache-nginx-and-openssl-for-forward-secrecy -# deeper down the article +# deeper down the article (without RC4) SSLProtocol all -SSLv2 -SSLv3 SSLHonorCipherOrder on -SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS +RC4 RC4" +SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !RC4" # HSTS (mod_headers is required) (15768000 seconds = 6 months) Header always add Strict-Transport-Security "max-age=15768000" From 94d9571e25fc0c8be895dd4516b043ef266354ed Mon Sep 17 00:00:00 2001 From: eric Date: Thu, 5 Mar 2015 17:35:55 -0500 Subject: [PATCH 10/19] flag kindle email addressed that don't contain "kindle" in them only valid kindle addresses we've seen not in kindle.com domain are kindle.cn. Wanted to allow other addresses just in case. --- frontend/templates/kindle_change_successful.html | 4 +++- frontend/templates/kindle_config.html | 5 +++-- frontend/views.py | 6 +++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/frontend/templates/kindle_change_successful.html b/frontend/templates/kindle_change_successful.html index 1967291f..15dc80eb 100644 --- a/frontend/templates/kindle_change_successful.html +++ b/frontend/templates/kindle_change_successful.html @@ -23,7 +23,9 @@ {% block ce_content %}

Kindle email change successful

-

Hooray! We can now send most unglued ebooks to you at {{ request.user.profile.kindle_email }}. Some ebooks are too big for us to send, though.

+ +

{% if ok_email %}Hooray! We can now send most unglued ebooks to you at {{ request.user.profile.kindle_email }}. Some ebooks are too big for us to send, though. + {% else %}{{ request.user.profile.kindle_email }} is probably not the right email for your Kindle; most Kindles use an @kindle.com email address. You can change it, but we'll try sending it anyway. {% endif %}

{% if work %}

We're now emailing you the ebook you wanted, {{ work.title }}... diff --git a/frontend/templates/kindle_config.html b/frontend/templates/kindle_config.html index bf05686d..ee821a68 100644 --- a/frontend/templates/kindle_config.html +++ b/frontend/templates/kindle_config.html @@ -33,7 +33,8 @@ (If you'd like to change your Kindle email, you can do so below. You'll need to download the book again.)

{% else %} -

You already have a Kindle email on file with Unglue.it: {{ kindle_email }} .

+

You already have a Kindle email on file with Unglue.it: {{ kindle_email }} . + {% if not ok_email %}That's probably not the right email; most Kindles use an @kindle.com email address. {% endif %}

You can change it below.

If you emailed yourself an Unglue.it ebook and got a message from Amazon that the sender is not in your approved email list, add notices@gluejar.com to your Approved Personal Document Email List under Personal Document Settings. @@ -43,7 +44,7 @@

Before your device or app can receive emails from Unglue.it, you'll have to add notices@gluejar.com to your Approved Personal Document Email List under Personal Document Settings.

-

Then, enter your Kindle email address below:

+

Then, enter your Kindle email address below (most Kindles use an @kindle.com email address.):

{% endif %} {% if work %}
{% csrf_token %} diff --git a/frontend/views.py b/frontend/views.py index c503d64d..5a7ef2be 100755 --- a/frontend/views.py +++ b/frontend/views.py @@ -3150,7 +3150,11 @@ def kindle_config(request, work_id=None): template = "kindle_change_successful.html" else: form = KindleEmailForm() - return render(request, template, {'form': form, 'work': work}) + return render(request, template, { + 'form': form, + 'work': work, + 'ok_email': request.user.profile.kindle_email and ('kindle' in request.user.profile.kindle_email), + }) @require_POST @csrf_exempt From 5057820f30bf740f490ad2587a7bc345e9673f89 Mon Sep 17 00:00:00 2001 From: eric Date: Thu, 5 Mar 2015 22:26:25 -0500 Subject: [PATCH 11/19] update for mac yosemite --- frontend/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/views.py b/frontend/views.py index c503d64d..91cf053d 100755 --- a/frontend/views.py +++ b/frontend/views.py @@ -2900,7 +2900,7 @@ class DownloadView(PurchaseView): 'iphone': 'iPhone' in agent, 'android': android, 'desktop': desktop, - 'mac_ibooks': 'Mac OS X 10.9' in agent or 'Mac OS X 10_9' in agent, + 'mac_ibooks': 'Mac OS X 10.9' in agent or 'Mac OS X 10_9' in agent or 'Mac OS X 10.10' in agent or 'Mac OS X 10_10' in agent, 'acq':acq, 'show_beg': self.show_beg, 'preapproval_amount': self.get_preapproval_amount(), From 0c4550fe4aaeb335dfb54254580a9efcb9efd912 Mon Sep 17 00:00:00 2001 From: eric Date: Thu, 5 Mar 2015 22:30:03 -0500 Subject: [PATCH 12/19] save ebook file size on send_to_kindle don't deal with booxtream (b2u) ebooks at a all, only deal with free ebooks --- .../0070_auto__add_field_ebook_filesize.py | 351 ++++++++++++++++++ core/models.py | 9 + frontend/views.py | 21 +- 3 files changed, 374 insertions(+), 7 deletions(-) create mode 100644 core/migrations/0070_auto__add_field_ebook_filesize.py diff --git a/core/migrations/0070_auto__add_field_ebook_filesize.py b/core/migrations/0070_auto__add_field_ebook_filesize.py new file mode 100644 index 00000000..cdc7ad99 --- /dev/null +++ b/core/migrations/0070_auto__add_field_ebook_filesize.py @@ -0,0 +1,351 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'Ebook.filesize' + db.add_column('core_ebook', 'filesize', + self.gf('django.db.models.fields.PositiveIntegerField')(null=True), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'Ebook.filesize' + db.delete_column('core_ebook', 'filesize') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'booxtream.boox': { + 'Meta': {'object_name': 'Boox'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'download_link_epub': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}), + 'download_link_mobi': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}), + 'downloads_remaining': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0'}), + 'expirydays': ('django.db.models.fields.PositiveSmallIntegerField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'referenceid': ('django.db.models.fields.CharField', [], {'max_length': '32'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.acq': { + 'Meta': {'object_name': 'Acq'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'lib_acq': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'loans'", 'null': 'True', 'to': "orm['core.Acq']"}), + 'license': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '1'}), + 'nonce': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}), + 'refreshed': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'refreshes': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 3, 5, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'acqs'", 'to': "orm['auth.User']"}), + 'watermarked': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['booxtream.Boox']", 'null': 'True'}), + 'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'acqs'", 'to': "orm['core.Work']"}) + }, + 'core.author': { + 'Meta': {'object_name': 'Author'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'editions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'authors'", 'symmetrical': 'False', 'to': "orm['core.Edition']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '500'}) + }, + 'core.badge': { + 'Meta': {'object_name': 'Badge'}, + 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '72', 'blank': 'True'}) + }, + 'core.campaign': { + 'Meta': {'object_name': 'Campaign'}, + 'activated': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}), + 'amazon_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'cc_date_initial': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'deadline': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}), + 'description': ('ckeditor.fields.RichTextField', [], {'null': 'True'}), + 'details': ('ckeditor.fields.RichTextField', [], {'null': 'True', 'blank': 'True'}), + 'do_watermark': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'dollar_per_day': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'edition': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'null': 'True', 'to': "orm['core.Edition']"}), + 'email': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'left': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2', 'db_index': 'True'}), + 'license': ('django.db.models.fields.CharField', [], {'default': "'CC BY-NC-ND'", 'max_length': '255'}), + 'managers': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'campaigns'", 'symmetrical': 'False', 'to': "orm['auth.User']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True'}), + 'paypal_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'publisher': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'null': 'True', 'to': "orm['core.Publisher']"}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'INITIALIZED'", 'max_length': '15', 'null': 'True', 'db_index': 'True'}), + 'target': ('django.db.models.fields.DecimalField', [], {'default': '0.0', 'null': 'True', 'max_digits': '14', 'decimal_places': '2'}), + 'type': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '1'}), + 'use_add_ask': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'to': "orm['core.Work']"}) + }, + 'core.campaignaction': { + 'Meta': {'object_name': 'CampaignAction'}, + 'campaign': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actions'", 'to': "orm['core.Campaign']"}), + 'comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'type': ('django.db.models.fields.CharField', [], {'max_length': '15'}) + }, + 'core.celerytask': { + 'Meta': {'object_name': 'CeleryTask'}, + 'active': ('django.db.models.fields.NullBooleanField', [], {'default': 'True', 'null': 'True', 'blank': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 3, 5, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'null': 'True'}), + 'function_args': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), + 'function_name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'task_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tasks'", 'null': 'True', 'to': "orm['auth.User']"}) + }, + 'core.claim': { + 'Meta': {'object_name': 'Claim'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'rights_holder': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'claim'", 'to': "orm['core.RightsHolder']"}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'active'", 'max_length': '7'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'claim'", 'to': "orm['auth.User']"}), + 'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'claim'", 'to': "orm['core.Work']"}) + }, + 'core.ebook': { + 'Meta': {'object_name': 'Ebook'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'download_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'edition': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ebooks'", 'to': "orm['core.Edition']"}), + 'filesize': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), + 'format': ('django.db.models.fields.CharField', [], {'max_length': '25'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'provider': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'rights': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'db_index': 'True'}), + 'url': ('django.db.models.fields.URLField', [], {'max_length': '1024'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}) + }, + 'core.ebookfile': { + 'Meta': {'object_name': 'EbookFile'}, + 'asking': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'edition': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ebook_files'", 'to': "orm['core.Edition']"}), + 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + 'format': ('django.db.models.fields.CharField', [], {'max_length': '25'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'core.edition': { + 'Meta': {'object_name': 'Edition'}, + 'cover_image': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'publication_date': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '50', 'null': 'True', 'blank': 'True'}), + 'publisher_name': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'editions'", 'null': 'True', 'to': "orm['core.PublisherName']"}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'}), + 'unglued': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'editions'", 'null': 'True', 'to': "orm['core.Work']"}) + }, + 'core.gift': { + 'Meta': {'object_name': 'Gift'}, + 'acq': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'gifts'", 'to': "orm['core.Acq']"}), + 'giver': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'gifts'", 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'message': ('django.db.models.fields.TextField', [], {'default': "''", 'max_length': '512'}), + 'to': ('django.db.models.fields.CharField', [], {'max_length': '75', 'blank': 'True'}), + 'used': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}) + }, + 'core.hold': { + 'Meta': {'object_name': 'Hold'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'library': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'holds'", 'to': "orm['libraryauth.Library']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'holds'", 'to': "orm['auth.User']"}), + 'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'holds'", 'to': "orm['core.Work']"}) + }, + 'core.identifier': { + 'Meta': {'unique_together': "(('type', 'value'),)", 'object_name': 'Identifier'}, + 'edition': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'identifiers'", 'null': 'True', 'to': "orm['core.Edition']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'type': ('django.db.models.fields.CharField', [], {'max_length': '4'}), + 'value': ('django.db.models.fields.CharField', [], {'max_length': '250'}), + 'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'identifiers'", 'to': "orm['core.Work']"}) + }, + 'core.key': { + 'Meta': {'object_name': 'Key'}, + 'encrypted_value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}) + }, + 'core.libpref': { + 'Meta': {'object_name': 'Libpref'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'marc_link_target': ('django.db.models.fields.CharField', [], {'default': "'UNGLUE'", 'max_length': '6'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'libpref'", 'unique': 'True', 'to': "orm['auth.User']"}) + }, + 'core.offer': { + 'Meta': {'object_name': 'Offer'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'license': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '1'}), + 'price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '6', 'decimal_places': '2'}), + 'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'offers'", 'to': "orm['core.Work']"}) + }, + 'core.premium': { + 'Meta': {'object_name': 'Premium'}, + 'amount': ('django.db.models.fields.DecimalField', [], {'max_digits': '10', 'decimal_places': '0'}), + 'campaign': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'premiums'", 'null': 'True', 'to': "orm['core.Campaign']"}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'limit': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'type': ('django.db.models.fields.CharField', [], {'max_length': '2'}) + }, + 'core.press': { + 'Meta': {'object_name': 'Press'}, + 'date': ('django.db.models.fields.DateField', [], {'db_index': 'True'}), + 'highlight': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}), + 'note': ('django.db.models.fields.CharField', [], {'max_length': '140', 'blank': 'True'}), + 'source': ('django.db.models.fields.CharField', [], {'max_length': '140'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '140'}), + 'url': ('django.db.models.fields.URLField', [], {'max_length': '200'}) + }, + 'core.publisher': { + 'Meta': {'object_name': 'Publisher'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'logo_url': ('django.db.models.fields.URLField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'key_publisher'", 'to': "orm['core.PublisherName']"}), + 'url': ('django.db.models.fields.URLField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}) + }, + 'core.publishername': { + 'Meta': {'object_name': 'PublisherName'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'publisher': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'alternate_names'", 'null': 'True', 'to': "orm['core.Publisher']"}) + }, + 'core.rightsholder': { + 'Meta': {'object_name': 'RightsHolder'}, + 'can_sell': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'email': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'rights_holder'", 'to': "orm['auth.User']"}), + 'rights_holder_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.subject': { + 'Meta': {'ordering': "['name']", 'object_name': 'Subject'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_visible': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'}), + 'works': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'subjects'", 'symmetrical': 'False', 'to': "orm['core.Work']"}) + }, + 'core.userprofile': { + 'Meta': {'object_name': 'UserProfile'}, + 'avatar_source': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '4', 'null': 'True'}), + 'badges': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'holders'", 'symmetrical': 'False', 'to': "orm['core.Badge']"}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'facebook_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), + 'goodreads_auth_secret': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'goodreads_auth_token': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'goodreads_user_id': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}), + 'goodreads_user_link': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'goodreads_user_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'home_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'kindle_email': ('django.db.models.fields.EmailField', [], {'max_length': '254', 'blank': 'True'}), + 'librarything_id': ('django.db.models.fields.CharField', [], {'max_length': '31', 'blank': 'True'}), + 'pic_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), + 'tagline': ('django.db.models.fields.CharField', [], {'max_length': '140', 'blank': 'True'}), + 'twitter_id': ('django.db.models.fields.CharField', [], {'max_length': '15', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': "orm['auth.User']"}) + }, + 'core.waswork': { + 'Meta': {'object_name': 'WasWork'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'moved': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}), + 'was': ('django.db.models.fields.IntegerField', [], {'unique': 'True'}), + 'work': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Work']"}) + }, + 'core.wishes': { + 'Meta': {'object_name': 'Wishes', 'db_table': "'core_wishlist_works'"}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'source': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '15', 'blank': 'True'}), + 'wishlist': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Wishlist']"}), + 'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'wishes'", 'to': "orm['core.Work']"}) + }, + 'core.wishlist': { + 'Meta': {'object_name': 'Wishlist'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'wishlist'", 'unique': 'True', 'to': "orm['auth.User']"}), + 'works': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'wishlists'", 'symmetrical': 'False', 'through': "orm['core.Wishes']", 'to': "orm['core.Work']"}) + }, + 'core.work': { + 'Meta': {'ordering': "['title']", 'object_name': 'Work'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}), + 'earliest_publication': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True'}), + 'featured': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_free': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'language': ('django.db.models.fields.CharField', [], {'default': "'en'", 'max_length': '5', 'db_index': 'True'}), + 'num_wishes': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'}), + 'openlibrary_lookup': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'selected_edition': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'selected_works'", 'null': 'True', 'to': "orm['core.Edition']"}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'}) + }, + 'libraryauth.library': { + 'Meta': {'object_name': 'Library'}, + 'approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'backend': ('django.db.models.fields.CharField', [], {'default': "'ip'", 'max_length': '10'}), + 'group': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'library'", 'unique': 'True', 'null': 'True', 'to': "orm['auth.Group']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '80'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'libraries'", 'to': "orm['auth.User']"}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'library'", 'unique': 'True', 'to': "orm['auth.User']"}) + } + } + + complete_apps = ['core'] \ No newline at end of file diff --git a/core/models.py b/core/models.py index 68fc5ae4..a8e67f78 100755 --- a/core/models.py +++ b/core/models.py @@ -1826,6 +1826,8 @@ class EbookFile(models.Model): except: return False +send_to_kindle_limit=7492232 + class Ebook(models.Model): FORMAT_CHOICES = settings.FORMATS RIGHTS_CHOICES = cc.CHOICES @@ -1835,12 +1837,19 @@ class Ebook(models.Model): provider = models.CharField(max_length=255) download_count = models.IntegerField(default=0) active = models.BooleanField(default=True) + filesize = models.PositiveIntegerField(null=True) # use 'PD-US', 'CC BY', 'CC BY-NC-SA', 'CC BY-NC-ND', 'CC BY-NC', 'CC BY-ND', 'CC BY-SA', 'CC0' rights = models.CharField(max_length=255, null=True, choices = RIGHTS_CHOICES, db_index=True) edition = models.ForeignKey('Edition', related_name='ebooks') user = models.ForeignKey(User, null=True) + def kindle_sendable(self): + if not self.filesize or self.filesize < send_to_kindle_limit: + return True + else: + return False + def set_provider(self): self.provider=Ebook.infer_provider(self.url) return self.provider diff --git a/frontend/views.py b/frontend/views.py index 91cf053d..e5aba731 100755 --- a/frontend/views.py +++ b/frontend/views.py @@ -2867,12 +2867,12 @@ class DownloadView(PurchaseView): #send to kindle try: - non_google_ebooks.filter(format='mobi')[0] - can_kindle = True + kindle_ebook = non_google_ebooks.filter(format='mobi')[0] + can_kindle = kindle_ebook.kindle_sendable() except IndexError: try: - non_google_ebooks.filter(format='pdf')[0] - can_kindle = True + kindle_ebook = non_google_ebooks.filter(format='pdf')[0] + can_kindle = kindle_ebook.kindle_sendable() except IndexError: can_kindle = False # configure the xfer url @@ -3185,7 +3185,9 @@ def send_to_kindle(request, work_id, javascript='0'): if acq: ebook_url = acq.get_mobi_url() ebook_format = 'mobi' + filesize = None title = acq.work.title + ebook=None else: non_google_ebooks = work.ebooks().exclude(provider='Google Books') try: @@ -3200,6 +3202,7 @@ def send_to_kindle(request, work_id, javascript='0'): ebook.increment() ebook_url = ebook.url ebook_format = ebook.format + filesize = ebook.filesize logger.info('ebook: {0}, user_ip: {1}'.format(work_id, request.META['REMOTE_ADDR'])) title = ebook.edition.title title = title.replace(' ', '_') @@ -3219,7 +3222,6 @@ def send_to_kindle(request, work_id, javascript='0'): """ - TO FIX rigorously: Amazon SES has a 10 MB size limit (http://aws.amazon.com/ses/faqs/#49) in messages sent to determine whether the file will meet this limit, we probably need to compare the size of the mime-encoded file to 10 MB. (and it's unclear exactly what the Amazon FAQ means precisely by @@ -3232,8 +3234,13 @@ def send_to_kindle(request, work_id, javascript='0'): This won't perfectly measure size of email, but should be safe, and is much faster than doing the check after download. """ filehandle = urllib.urlopen(ebook_url) - filesize = int(filehandle.info().getheaders("Content-Length")[0]) - if filesize > 7492232: + if not filesize: + filesize = int(filehandle.info().getheaders("Content-Length")[0]) + if ebook: + ebook.filesize = filesize if filesize < 2147483647 else 2147483647 # largest safe positive integer + ebook.save() + + if filesize > models.send_to_kindle_limit: logger.info('ebook %s is too large to be emailed' % work.id) return local_response(request, javascript, context, 0) From 355e50b2973cb0a87979770564ac09bb86f1c4f0 Mon Sep 17 00:00:00 2001 From: eric Date: Fri, 6 Mar 2015 16:32:21 -0500 Subject: [PATCH 13/19] update news link --- frontend/templates/home.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/templates/home.html b/frontend/templates/home.html index f3be6b16..62eaad7b 100755 --- a/frontend/templates/home.html +++ b/frontend/templates/home.html @@ -179,7 +179,7 @@ function put_un_in_cookie2(){
From 71c8a95b9071fdbcd1a6e08743cebbd3204b6c94 Mon Sep 17 00:00:00 2001 From: Raymond Yee Date: Fri, 6 Mar 2015 15:16:04 -0800 Subject: [PATCH 14/19] add comment about what fix_kindle_bugs does --- core/models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/models.py b/core/models.py index 9767375d..abab5bcc 100755 --- a/core/models.py +++ b/core/models.py @@ -1189,6 +1189,9 @@ class Work(models.Model): return '' def kindle_safe_title(self): + """ + Removes accents, keeps letters and numbers, replaces non-Latin characters with "#", and replaces punctuation with "_" + """ safe = u'' nkfd_form = unicodedata.normalize('NFKD', self.title) #unaccent accented letters for c in nkfd_form: From c3896b70d46971935d311900717aaca514fc1316 Mon Sep 17 00:00:00 2001 From: eric Date: Tue, 10 Mar 2015 14:18:34 -0400 Subject: [PATCH 15/19] allow ebook creation without filesize --- frontend/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/forms.py b/frontend/forms.py index 50b316e8..52ebe1ca 100644 --- a/frontend/forms.py +++ b/frontend/forms.py @@ -214,7 +214,7 @@ class EbookFileForm(forms.ModelForm): class EbookForm(forms.ModelForm): class Meta: model = Ebook - exclude =( 'created', 'download_count', 'active') + exclude =( 'created', 'download_count', 'active', 'filesize') widgets = { 'edition': forms.HiddenInput, 'user': forms.HiddenInput, From 3bdec1327025b9c3ebcf0eab41b222f087463e2c Mon Sep 17 00:00:00 2001 From: eric Date: Tue, 10 Mar 2015 14:19:05 -0400 Subject: [PATCH 16/19] display ebook errors for first ebook --- frontend/templates/edition_upload.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/templates/edition_upload.html b/frontend/templates/edition_upload.html index 9695afd7..0391f3cb 100644 --- a/frontend/templates/edition_upload.html +++ b/frontend/templates/edition_upload.html @@ -2,10 +2,10 @@ {% if edition.ebook_form %} {% if show_ebook_form %}
- +{% if alert %}
{{alert}}
{% endif %} {% if edition.ebooks.all.0 %}

eBooks for this Edition

- {% if alert %}
{{alert}}
{% endif %} + {% for ebook in edition.ebooks.all %} {{ ebook.format }} {{ebook.rights}} at {{ebook.provider}}. Downloaded {{ ebook.download_count }} times.
{% endfor %} From cdb84dfffa92646e35fdf7933fcf976f10fd54a5 Mon Sep 17 00:00:00 2001 From: Raymond Yee Date: Tue, 10 Mar 2015 15:48:02 -0700 Subject: [PATCH 17/19] Working conclusion: use the configuration: SSLProtocol all -SSLv2 -SSLv3 SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA SSLHonorCipherOrder on --- deploy/just.conf | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/deploy/just.conf b/deploy/just.conf index 3a448f20..5f353814 100644 --- a/deploy/just.conf +++ b/deploy/just.conf @@ -16,12 +16,13 @@ Redirect permanent / https://just.unglue.it/ SSLEngine on ServerName just.unglue.it:443 -# https://community.qualys.com/blogs/securitylabs/2013/08/05/configuring-apache-nginx-and-openssl-for-forward-secrecy -# deeper down the article (without RC4) +# generated using https://mozilla.github.io/server-side-tls/ssl-config-generator/ +# intermediate mode +# 2015.03.04 (with Apache v 2.2.22 and OpenSSL 1.0.1 and HSTS enabled) -SSLProtocol all -SSLv2 -SSLv3 -SSLHonorCipherOrder on -SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !RC4" +SSLProtocol all -SSLv2 -SSLv3 +SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA +SSLHonorCipherOrder on # HSTS (mod_headers is required) (15768000 seconds = 6 months) Header always add Strict-Transport-Security "max-age=15768000" From 949f22415b5995d62731fc7fff18bf36aeb05009 Mon Sep 17 00:00:00 2001 From: Raymond Yee Date: Tue, 10 Mar 2015 16:07:15 -0700 Subject: [PATCH 18/19] a modern configuration from https://mozilla.github.io/server-side-tls/ssl-config-generator/ --- deploy/just.conf | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/deploy/just.conf b/deploy/just.conf index 5f353814..de30d0b8 100644 --- a/deploy/just.conf +++ b/deploy/just.conf @@ -17,11 +17,12 @@ SSLEngine on ServerName just.unglue.it:443 # generated using https://mozilla.github.io/server-side-tls/ssl-config-generator/ -# intermediate mode -# 2015.03.04 (with Apache v 2.2.22 and OpenSSL 1.0.1 and HSTS enabled) +# modern mode +# 2015.03.10 (with Apache v 2.2.22 and OpenSSL 1.0.1 and HSTS enabled) -SSLProtocol all -SSLv2 -SSLv3 -SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA + +SSLProtocol all -SSLv2 -SSLv3 -TLSv1 +SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK SSLHonorCipherOrder on # HSTS (mod_headers is required) (15768000 seconds = 6 months) From 486e474fc3801590f2329acd60f7db1ab62d3abf Mon Sep 17 00:00:00 2001 From: Raymond Yee Date: Wed, 11 Mar 2015 10:10:48 -0700 Subject: [PATCH 19/19] Set the SSL configuration to that generated by https://mozilla.github.io/server-side-tls/ssl-config-generator/ intermediate mode 2015.03.04 (with Apache v 2.2.22 and OpenSSL 1.0.1 and HSTS enabled) --- deploy/just.conf | 9 ++++----- deploy/prod.conf | 10 +++++++++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/deploy/just.conf b/deploy/just.conf index de30d0b8..5f353814 100644 --- a/deploy/just.conf +++ b/deploy/just.conf @@ -17,12 +17,11 @@ SSLEngine on ServerName just.unglue.it:443 # generated using https://mozilla.github.io/server-side-tls/ssl-config-generator/ -# modern mode -# 2015.03.10 (with Apache v 2.2.22 and OpenSSL 1.0.1 and HSTS enabled) +# intermediate mode +# 2015.03.04 (with Apache v 2.2.22 and OpenSSL 1.0.1 and HSTS enabled) - -SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK +SSLProtocol all -SSLv2 -SSLv3 +SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA SSLHonorCipherOrder on # HSTS (mod_headers is required) (15768000 seconds = 6 months) diff --git a/deploy/prod.conf b/deploy/prod.conf index f3baaee4..b6860b99 100644 --- a/deploy/prod.conf +++ b/deploy/prod.conf @@ -14,7 +14,15 @@ Redirect permanent / https://unglue.it/ ServerName unglue.it:443 SSLEngine on -SSLProtocol All -SSLv2 -SSLv3 + +# generated using https://mozilla.github.io/server-side-tls/ssl-config-generator/ +# intermediate mode +# 2015.03.04 (with Apache v 2.2.22 and OpenSSL 1.0.1 and HSTS enabled) + +SSLProtocol all -SSLv2 -SSLv3 +SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA +SSLHonorCipherOrder on + SSLCertificateFile /etc/ssl/certs/server.crt SSLCertificateKeyFile /etc/ssl/private/server.key SSLCertificateChainFile /etc/ssl/certs/STAR_unglue_it.ca-bundle