Skip to content

Commit 3a0a950

Browse files
committed
Version 2.3.0
1 parent c28e4ce commit 3a0a950

File tree

4 files changed

+105
-38
lines changed

4 files changed

+105
-38
lines changed

README.md

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,16 @@ The content is encrypted with AES-256 in Python using PyCryptodome, and decrypte
3131
* [Usage](#usage)
3232
* [Global password protection](#global-password-protection)
3333
* [Secret from environment](#secret-from-environment)
34-
* [Customization](#extra-vars-customization)
34+
* [Customization](#default-vars-customization)
3535
* [Features](#features)
3636
* [HighlightJS support](#highlightjs-support) *(default)*
3737
* [Arithmatex support](#arithmatex-support) *(default)*
3838
* [Mermaid2 support](#mermaid-support) *(default)*
3939
* [Tag encrypted page](#tag-encrypted-page) *(default)*
40-
* [Rebember password](#rebember-password)
40+
* [Remember password](#remember-password)
4141
* [Encrypt something](#encrypt-something)
4242
* [Search index encryption](#search-index-encryption)
43+
* [Override default templates](#override-default-templates)
4344
* [Add button](#add-button)
4445
* [Reload scripts](#reload-scripts)
4546
* [Contributing](#contributing)
@@ -58,7 +59,7 @@ Install the package from source with pip:
5859
```bash
5960
cd mkdocs-encryptcontent-plugin/
6061
python3 setup.py sdist bdist_wheel
61-
pip3 install dist/mkdocs_encryptcontent_plugin-2.2.1-py3-none-any.whl
62+
pip3 install dist/mkdocs_encryptcontent_plugin-2.3.0-py3-none-any.whl
6263
```
6364

6465
Enable the plugin in your `mkdocs.yml`:
@@ -111,7 +112,7 @@ plugins:
111112
> **NOTE** Keep in mind that if the `use_secret:` configuration is set, it will always be used even if you have also set a global password with the `global_password` variable.
112113

113114

114-
### Extra vars customization
115+
### Default vars customization
115116

116117
Optionally you can use some extra variables in plugin configuration to customize default messages.
117118

@@ -218,7 +219,7 @@ For example, in your theme template, you can use conditional check to add custom
218219
<a {% if nav_item.encrypted %}class="mkdocs-encrypted-class"{% endif %}href="{{ nav_item.url|url }}">{{ nav_item.title }}</a>
219220
```
220221

221-
### Rebember password
222+
### Remember password
222223

223224
Related to [issue #6](https://github.com/CoinK0in/mkdocs-encryptcontent-plugin/issues/6)
224225

@@ -240,14 +241,14 @@ If your password is a [global password](#global-password-protection), you can fi
240241
The key that will be created with a generic name to making it accessible, by default, on all the pages of your site.
241242

242243
The form of decryption remains visible as long as the content has not been successfully decrypted, which allows in case of error to retry.
243-
All keys created with this feature on localstorage have an default expire time daly set to 24 hours, just cause ...
244+
All keys created with this feature on sessionStorage/localStorage have an default expire time daly set to 24 hours, just cause ...
244245

245-
However *(optionally)*, its possible to change the default expire time by setting options `default_expire_dalay: <number>` in your `mkdocs.yml`. Your configuration should look like this when you enabled this feature :
246+
However *(optionally)*, its possible to change the default expire time by setting options `default_expire_delay: <number>` in your `mkdocs.yml`. Your configuration should look like this when you enabled this feature :
246247
```yaml
247248
plugins:
248249
- encryptcontent:
249250
remember_password: True
250-
default_expire_dalay: 24 # <-- Default expire delay in hours (optional)
251+
default_expire_delay: 24 # <-- Default expire delay in hours (optional)
251252
```
252253

253254
> **NOTE** The expired elements of the localStorage are only deleted by the execution of the decrypt-content.js scripts and therefore by the navigation on the site. Secret items can therefore remain visible in local storage after their expiration dates.
@@ -371,6 +372,42 @@ When the configuration mode is set to "**dynamically**", the [javascripts contri
371372

372373
> **NOTE** The mode 'dynamically' is currently **not compatible with Material Theme** !
373374

375+
### Override default templates
376+
377+
Related to [issue #32](https://github.com/CoinK0in/mkdocs-encryptcontent-plugin/issues/32)
378+
379+
You can override the default templates with your own templates by providing an actual replacement path in the `html_template_path` *(HTML)* and `js_template_path` *(JS)* directives. Overridden templates **completely** replace the default templates. You **must** therefore copy the default templates to keep this module working.
380+
381+
```yaml
382+
plugins:
383+
- encryptcontent:
384+
html_template_path: "/root/real/path/mkdocs_poc/my_html_template.html"
385+
js_template_path: "/root/real/path/mkdocs_poc/my_js_template.js"
386+
```
387+
388+
When you overriding the default templates, you can add and use your own Jinja variables to condition and enrich your template, by defining `html_extra_vars` and `js_extra_vars` directives in key/value format. Added values can be used in your Jinja templates via the variable `extra`.
389+
390+
```yaml
391+
plugins:
392+
- encryptcontent:
393+
html_extra_vars:
394+
my_extra: "extra value"
395+
<key>: <value>
396+
js_extra_vars:
397+
my_extra: "extra value"
398+
<key>: <value>
399+
```
400+
401+
For example, you can modify your HTML template, to add a new title with your own text variable.
402+
403+
```jinja
404+
[ ... ]
405+
<h2>{{ extra.my_extra }}</h2>
406+
[ ... ]
407+
```
408+
409+
> **NOTE** You must avoid replacing/overwriting the variables used by default by this module. The limitations are the same as those of the jinja models. Issues related to template override will not be addressed.
410+
374411
### Add button
375412

376413
Add `password_button: True` in plugin configuration variable, to add button to the right of the password field.

encryptcontent/decrypt-contents.tpl.js

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,27 +32,43 @@ function decrypt_content(password, iv_b64, ciphertext_b64, padding_char) {
3232
};
3333

3434
{% if remember_password -%}
35-
/* Set key:value with expire time in localStorage */
35+
/* Set key:value with expire time in sessionStorage/localStorage */
3636
function setItemExpiry(key, value, ttl) {
3737
const now = new Date()
3838
const item = {
3939
value: encodeURIComponent(value),
4040
expiry: now.getTime() + ttl,
4141
}
42+
{% if session_storage -%}
43+
sessionStorage.setItem('encryptcontent_' + encodeURIComponent(key), JSON.stringify(item))
44+
{%- else %}
4245
localStorage.setItem('encryptcontent_' + encodeURIComponent(key), JSON.stringify(item))
46+
{%- endif %}
4347
};
4448

45-
/* Delete key with specific name in localStorage */
49+
/* Delete key with specific name in sessionStorage/localStorage */
4650
function delItemName(key) {
51+
{% if session_storage -%}
52+
sessionStorage.removeItem('encryptcontent_' + encodeURIComponent(key));
53+
{%- else %}
4754
localStorage.removeItem('encryptcontent_' + encodeURIComponent(key));
55+
{%- endif %}
4856
};
4957

50-
/* Get key:value from localStorage */
58+
/* Get key:value from sessionStorage/localStorage */
5159
function getItemExpiry(key) {
60+
{% if session_storage -%}
61+
var remember_password = sessionStorage.getItem('encryptcontent_' + encodeURIComponent(key));
62+
{%- else %}
5263
var remember_password = localStorage.getItem('encryptcontent_' + encodeURIComponent(key));
64+
{%- endif %}
5365
if (!remember_password) {
5466
// fallback to search default password defined by path
67+
{% if session_storage -%}
68+
var remember_password = sessionStorage.getItem('encryptcontent_' + encodeURIComponent("/"));
69+
{%- else %}
5570
var remember_password = localStorage.getItem('encryptcontent_' + encodeURIComponent("/"));
71+
{%- endif %}
5672
if (!remember_password) {
5773
return null
5874
}
@@ -61,7 +77,7 @@ function getItemExpiry(key) {
6177
const now = new Date()
6278
if (now.getTime() > item.expiry) {
6379
// if the item is expired, delete the item from storage and return null
64-
localStorage.removeItem('encryptcontent_' + encodeURIComponent(key))
80+
delItemName(key)
6581
return null
6682
}
6783
return decodeURIComponent(item.value)
@@ -202,7 +218,7 @@ function init_decryptor() {
202218
{%- endif %}
203219

204220
{% if remember_password -%}
205-
/* If remember_password is set, try to use localstorage item to decrypt content when page is loaded */
221+
/* If remember_password is set, try to use sessionStorage/localstorage item to decrypt content when page is loaded */
206222
var password_cookie = getItemExpiry(window.location.pathname);
207223
if (password_cookie) {
208224
password_input.value = password_cookie;
@@ -218,7 +234,7 @@ function init_decryptor() {
218234
var something_decrypted = decrypt_somethings(password_input, encrypted_something);
219235
{%- endif %}
220236
} else {
221-
// remove item on localStorage if decryption process fail (Invalid item)
237+
// remove item on sessionStorage/localStorage if decryption process fail (Invalid item)
222238
delItemName(window.location.pathname)
223239
}
224240
};
@@ -234,8 +250,8 @@ function init_decryptor() {
234250
);
235251
if (content_decrypted) {
236252
{% if remember_password -%}
237-
// keep password value on localStorage with specific path (relative)
238-
setItemExpiry(document.location.pathname, password_input.value, 1000*3600*{{ default_expire_dalay | int }});
253+
// keep password value on sessionStorage/localStorage with specific path (relative)
254+
setItemExpiry(document.location.pathname, password_input.value, 1000*3600*{{ default_expire_delay | int }});
239255
{%- endif %}
240256
// continue to decrypt others parts
241257
{% if experimental -%}
@@ -266,8 +282,8 @@ function init_decryptor() {
266282
);
267283
if (content_decrypted) {
268284
{% if remember_password -%}
269-
// keep password value on localStorage with specific path (relative)
270-
setItemExpiry(location_path, password_input.value, 1000*3600*{{ default_expire_dalay | int }});
285+
// keep password value on sessionStorage/localStorage with specific path (relative)
286+
setItemExpiry(location_path, password_input.value, 1000*3600*{{ default_expire_delay | int }});
271287
{%- endif %}
272288
// continue to decrypt others parts
273289
{% if experimental -%}

encryptcontent/plugin.py

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,11 @@
2727
]
2828

2929
PLUGIN_DIR = os.path.dirname(os.path.realpath(__file__))
30-
DECRYPT_FORM_TPL_PATH = os.path.join(PLUGIN_DIR, 'decrypt-form.tpl.html')
31-
DECRYPT_JS_TPL_PATH = os.path.join(PLUGIN_DIR, 'decrypt-contents.tpl.js')
32-
33-
with open(DECRYPT_FORM_TPL_PATH, 'r') as template_html:
34-
DECRYPT_FORM_TPL = template_html.read()
35-
36-
with open(DECRYPT_JS_TPL_PATH, 'r') as template_js:
37-
DECRYPT_JS_TPL = template_js.read()
3830

3931
SETTINGS = {
4032
'title_prefix': '[Protected] ',
4133
'summary': 'This content is protected with AES encryption. ',
42-
'placeholder': 'Provide password and press ENTER',
34+
'placeholder': 'Use CTRL+ENTER to provide global password',
4335
'password_button_text': 'Decrypt',
4436
'decryption_failure_message': 'Invalid password.',
4537
'encryption_info_message': 'Contact your administrator for access to this page.'
@@ -53,22 +45,32 @@ class encryptContentPlugin(BasePlugin):
5345
""" Plugin that encrypt markdown content with AES and inject decrypt form. """
5446

5547
config_scheme = (
56-
('use_secret', config_options.Type(string_types, default=None)),
48+
# default customization
5749
('title_prefix', config_options.Type(string_types, default=str(SETTINGS['title_prefix']))),
5850
('summary', config_options.Type(string_types, default=str(SETTINGS['summary']))),
5951
('placeholder', config_options.Type(string_types, default=str(SETTINGS['placeholder']))),
6052
('decryption_failure_message', config_options.Type(string_types, default=str(SETTINGS['decryption_failure_message']))),
6153
('encryption_info_message', config_options.Type(string_types, default=str(SETTINGS['encryption_info_message']))),
6254
('password_button_text', config_options.Type(string_types, default=str(SETTINGS['password_button_text']))),
55+
('password_button', config_options.Type(bool, default=False)),
56+
# password feature
6357
('global_password', config_options.Type(string_types, default=None)),
58+
('use_secret', config_options.Type(string_types, default=None)),
6459
('password', config_options.Type(string_types, default=None)),
60+
('remember_password', config_options.Type(bool, default=False)),
61+
('session_storage', config_options.Type(bool, default=True)),
62+
('default_expire_delay', config_options.Type(int, default=int(24))),
63+
# default features enabled
6564
('arithmatex', config_options.Type(bool, default=True)),
6665
('hljs', config_options.Type(bool, default=True)),
6766
('mermaid2', config_options.Type(bool, default=True)),
68-
('remember_password', config_options.Type(bool, default=False)),
69-
('default_expire_dalay', config_options.Type(int, default=int(24))),
7067
('tag_encrypted_page', config_options.Type(bool, default=True)),
71-
('password_button', config_options.Type(bool, default=False)),
68+
# override feature
69+
('html_template_path', config_options.Type(string_types, default=str(os.path.join(PLUGIN_DIR, 'decrypt-form.tpl.html')))),
70+
('html_extra_vars', config_options.Type(dict, default={})),
71+
('js_template_path', config_options.Type(string_types, default=str(os.path.join(PLUGIN_DIR, 'decrypt-contents.tpl.js')))),
72+
('js_extra_vars', config_options.Type(dict, default={})),
73+
# others features
7274
('encrypted_something', config_options.Type(dict, default={})),
7375
('search_index', config_options.Choice(('clear', 'dynamically', 'encrypted'), default='encrypted')),
7476
('reload_scripts', config_options.Type(list, default=[])),
@@ -104,7 +106,7 @@ def __encrypt_text_aes__(self, text, password):
104106
def __encrypt_content__(self, content, base_path):
105107
""" Replaces page or article content with decrypt form. """
106108
ciphertext_bundle = self.__encrypt_text_aes__(content, str(self.config['password']))
107-
decrypt_form = Template(DECRYPT_FORM_TPL).render({
109+
decrypt_form = Template(self.config['html_template']).render({
108110
# custom message and template rendering
109111
'summary': self.config['summary'],
110112
'placeholder': self.config['placeholder'],
@@ -115,13 +117,15 @@ def __encrypt_content__(self, content, base_path):
115117
# otherwise the output string will be wrapped with b''
116118
'ciphertext_bundle': b';'.join(ciphertext_bundle).decode('ascii'),
117119
'js_libraries': JS_LIBRARIES,
118-
'base_path': base_path
120+
'base_path': base_path,
121+
# add extra vars
122+
'extra': self.config['html_extra_vars']
119123
})
120124
return decrypt_form
121125

122126
def __generate_decrypt_js__(self):
123127
""" Generate JS file with enable feature. """
124-
decrypt_js = Template(DECRYPT_JS_TPL).render({
128+
decrypt_js = Template(self.config['js_template']).render({
125129
# custom message and template rendering
126130
'password_button': self.config['password_button'],
127131
'decryption_failure_message': self.config['decryption_failure_message'],
@@ -130,10 +134,13 @@ def __generate_decrypt_js__(self):
130134
'hljs': self.config['hljs'],
131135
'mermaid2': self.config['mermaid2'],
132136
'remember_password': self.config['remember_password'],
133-
'default_expire_dalay': int(self.config['default_expire_dalay']),
137+
'session_storage': self.config['session_storage'],
138+
'default_expire_delay': int(self.config['default_expire_delay']),
134139
'encrypted_something': self.config['encrypted_something'],
135140
'reload_scripts': self.config['reload_scripts'],
136-
'experimental': self.config['experimental']
141+
'experimental': self.config['experimental'],
142+
# add extra vars
143+
'extra': self.config['js_extra_vars']
137144
})
138145
return decrypt_js
139146

@@ -148,6 +155,13 @@ def on_config(self, config, **kwargs):
148155
:param config: global configuration object (mkdocs.yml)
149156
:return: global configuration object modified to include templates files
150157
"""
158+
# Override default template
159+
logger.debug('Load HTML template from file: "{file}".'.format(file=str(self.config['html_template_path'])))
160+
with open(self.config['html_template_path'], 'r') as template_html:
161+
self.config['html_template'] = template_html.read()
162+
logger.debug('Load JS template from file: "{file}".'.format(file=str(self.config['js_template_path'])))
163+
with open(self.config['js_template_path'], 'r') as template_js:
164+
self.config['js_template'] = template_js.read()
151165
# Optionnaly use Github secret
152166
if self.config.get('use_secret'):
153167
if os.environ.get(str(self.config['use_secret'])):
@@ -156,7 +170,7 @@ def on_config(self, config, **kwargs):
156170
logger.error('Cannot get global password from environment variable: {var}. Abort !'.format(
157171
var=str(self.config['use_secret']))
158172
)
159-
os._exit(1)
173+
os._exit(1) # prevent build without password to avoid leak
160174
# Set global password as default password for each page
161175
self.config['password'] = self.config['global_password']
162176
# Check if hljs feature need to be enabled, based on theme configuration

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def read(fname):
1111

1212
setup(
1313
name='mkdocs-encryptcontent-plugin',
14-
version='2.2.1',
14+
version='2.3.0',
1515
author='CoinK0in',
1616
author_email='[email protected]',
1717
description='A MkDocs plugin that encrypt/decrypt markdown content with AES',

0 commit comments

Comments
 (0)