Repository: freedomofpress/securedrop

Issue:
Create support for theming
The securedrop instance that the New Yorker uses is called StrongBox, and other orgs will want their own custom branding too. Right now there's no way to brand your own securedrop without hacking core code, which we should discourage.

This doesn't need to be in 0.1.



Discussion:
At globaleaks we made the UI as a self-contained, compiled Javascript application and used AngularJS framework with Bootstrap, by setting up a templating system as follow.

By using this choice of compressed GLClient the application preload quickly on TorHS and it's highly interactive also in presence of low latency.

To get an idea on how globaleaks client can be customized https://github.com/globaleaks/GlobaLeaks/wiki/Customization-guide if it can provide some ideas on the UI's framework

I think the main immediate need, in this case, is to provide an interface to let media organizations upload their own logo, and possibly a tagline, or about text somewhere.

I put this in the 0.2 milestone. Soon many news organizations will be running SecureDrop, and I'm sure they'll want to at least have their logo.

In the future we could also offer much more control over the theme.

Forbes's deployment is an example of a basic theme (replacing the logo)

On what level are people interested in theming SecureDrop? Would it be editing config.py to set the tagline and intro text? A SASS or LESS file that can generate the main CSS?

I'm imagining two components: a template that contains blocks of formatting (for the header, footer, body on the landing page, etc.), and an optional CSS file, `theme.css`, that is loaded last and can be used to customize appearance. That way, the site can easily inject their own copy into the existing site design if they wish (including their own images/logo/branding), but also have the option to change the styles too.

I think it would be good to start with just a simple Jinja2/HTML template file (`templates/theme.html`) and CSS (`static/css/theme.css`) to provide the basic functionality with minimal changes. We can investigate more involved solutions, like SASS/LESS, later as we get more feedback on the deploy process.

I'm working on this, starting with CSS theming and logo replacement.


Resolving patch:
diff --git a/securedrop/example_config.py b/securedrop/example_config.py
--- a/securedrop/example_config.py
+++ b/securedrop/example_config.py
@@ -1,5 +1,21 @@
 import os, stat
 
+### Application Configuration
+
+SOURCE_TEMPLATES_DIR = './source_templates'
+JOURNALIST_TEMPLATES_DIR = './journalist_templates'
+WORD_LIST = './wordlist'
+NOUNS='./dictionaries/nouns.txt'
+ADJECTIVES='./dictionaries/adjectives.txt'
+JOURNALIST_KEY='' # fingerprint of the public key for encrypting submissions
+BCRYPT_SALT=''    # bcrypt.gensalt()
+
+### Theming Options
+
+# If you want a custom image at the top, copy your png or jpg to static/i and 
+# update this to it's filename (e.g. "logo.jpg") .
+CUSTOM_IMAGE = None
+
 ### Flask Configurations
 
 class BaseConfig(object):
@@ -18,15 +34,6 @@ class TestingConfig(BaseConfig):
     # Tests are simpler if CSRF protection is disabled
     WTF_CSRF_ENABLED = False
 
-### Application Configuration
-
-SOURCE_TEMPLATES_DIR = './source_templates'
-JOURNALIST_TEMPLATES_DIR = './journalist_templates'
-WORD_LIST = './wordlist'
-
-JOURNALIST_KEY='' # fingerprint of the public key for encrypting submissions
-BCRYPT_SALT=''    # bcrypt.gensalt()
-
 if os.environ.get('SECUREDROP_ENV') == 'test':
     FlaskConfig=TestingConfig
     SECUREDROP_ROOT='/tmp/securedrop_test'
diff --git a/securedrop/journalist.py b/securedrop/journalist.py
--- a/securedrop/journalist.py
+++ b/securedrop/journalist.py
@@ -18,6 +18,12 @@
 CsrfProtect(app)
 
 app.jinja_env.globals['version'] = version.__version__
+if config.CUSTOM_IMAGE:
+    app.jinja_env.globals['header_image'] = config.CUSTOM_IMAGE
+    app.jinja_env.globals['use_header_image'] = True
+else:
+    app.jinja_env.globals['header_image'] = 'securedrop.png'
+    app.jinja_env.globals['use_header_image'] = False
 
 
 def get_docs(sid):
diff --git a/securedrop/source.py b/securedrop/source.py
--- a/securedrop/source.py
+++ b/securedrop/source.py
@@ -21,7 +21,12 @@
 CsrfProtect(app)
 
 app.jinja_env.globals['version'] = version.__version__
-
+if config.CUSTOM_IMAGE:
+    app.jinja_env.globals['header_image'] = config.CUSTOM_IMAGE
+    app.jinja_env.globals['use_header_image'] = True
+else:
+    app.jinja_env.globals['header_image'] = 'securedrop.png'
+    app.jinja_env.globals['use_header_image'] = False
 
 def logged_in():
     if 'logged_in' in session:


Test patch: