unverbuggt / mkdocs-encryptcontent-plugin

A MkDocs plugin that encrypt/decrypt markdown content with AES
https://unverbuggt.github.io/mkdocs-encryptcontent-plugin/
MIT License
123 stars 15 forks source link

Add global_path variable to use as fallback when there is a global pa… #28

Closed unverbuggt closed 2 years ago

unverbuggt commented 2 years ago

Add global_path variable to use as fallback when there is a global password defined or a members area shares the same password.

CoinK0in commented 2 years ago

Hey,

First of all, thanks for the PR. If I understood correctly, you want to limit the use of the global password to a specific area of ​​the site (/members/ in your example).

But currently this plugin doesn't know how to protect a page based on its path (URL). The global password protects ALL the pages by default (Or nothing / Or not the pages with empty meta password). To be precise, you must use "meta" tags in your markdown files. You can do the same thing by specifying in each of your pages, present in your member area, the password in the "meta" header. Which can be tedious depending on how many pages you have (I admit)

Currently, the changes you have made to your PR are not sufficient to create this functionality. You are only changing the JS method to retrieve the password, in the case of using the "remember password" function.

I'm missing something ?

unverbuggt commented 2 years ago

Hi,

if a global_password is set the script looks for the local storage at / as a fallback (if remember_password is set) - which is a good idea, because you only want to enter the global_password once. The problem in my case was that my site doesn't render to "/" but to "/site", so the fallback didn't work in this case... maybe it would be better to name it root_path or fallback_path instead of global_path.

CoinK0in commented 2 years ago

Hmm, I think I understand your problem.

Using the global password, with the remember password feature, set by default an "/" in the local storage location. But on your site, root URL is changed and the root can be /foo/ as described here : https://www.mkdocs.org/user-guide/configuration/#site_url.

Do you use this feature of Mkdocs ?

This fallback check in JS configuration comes from the fact that before being in local storage, the password was stored in browser cookies. I had to modify some functionalities for notions of security, related to JS access to cookies on modern browsers.

But I digress. In this case, it could be that the fallback/search of the default password no longer works because it is defined with "/" which is no longer the root URL of your site. I have not tested and validated this problem for the moment it is only a hypothesis based on your explanations. But that seems possible to me.

Could you confirm : 1 - You don't want to make a default password based on the url (Because it's very complicated for me) ? 2 - You are using a default root which is not "/" by default (Like example.com/foo/) 3 - Do you use the "site_url" variable in your mkdocs.yaml configuration to specify the new root path ?

I will do some tests on my side during the week to validate my hypothesis and correct this problem ASAP if it's validated.

unverbuggt commented 2 years ago

1(no, I don't want that), 2(yes), 3(yes).

see these two example configurations: encryptcontent_global_password_site_url.zip encryptcontent_members_area.zip

I probably should have thought more about this PR.. Is it possible to convert it to an issue or change the commit it is referrig to? Because I found a better soltion:

plugin.py:

@@ -66,6 +66,7 @@ class encryptContentPlugin(BasePlugin):
         ('hljs', config_options.Type(bool, default=True)),
         ('mermaid2', config_options.Type(bool, default=True)),
         ('remember_password', config_options.Type(bool, default=False)),
+        ('fallback_path', config_options.Type(string_types, default='/')),
         ('default_expire_dalay', config_options.Type(int, default=int(24))),
         ('tag_encrypted_page', config_options.Type(bool, default=True)),
         ('password_button', config_options.Type(bool, default=False)),
@@ -130,6 +131,7 @@ class encryptContentPlugin(BasePlugin):
             'hljs': self.config['hljs'],
             'mermaid2': self.config['mermaid2'],
             'remember_password': self.config['remember_password'],
+            'fallback_path': self.config['fallback_path'],
             'default_expire_dalay': int(self.config['default_expire_dalay']),
             'encrypted_something': self.config['encrypted_something'],
             'reload_scripts': self.config['reload_scripts'],
@@ -262,6 +264,8 @@ class encryptContentPlugin(BasePlugin):
                 config['theme'].dirs.append(path)
                 if 'search/main.js' not in config['extra_javascript']:
                     config['extra_javascript'].append('search/main.js')
+            if self.config['fallback_path'] == '/' and config['site_url'] != '':
+                self.config['fallback_path'] = '/' + config['site_url'].split('/',3)[3]
         except Exception as exp:
             logger.exception(exp)

decrypt-contents.tpl.js:

@@ -52,7 +52,7 @@ function getItemExpiry(key) {
     var remember_password = localStorage.getItem('encryptcontent_' + encodeURIComponent(key));
     if (!remember_password) {
         // fallback to search default password defined by path
-        var remember_password = localStorage.getItem('encryptcontent_' + encodeURIComponent("/"));
+        var remember_password = localStorage.getItem('encryptcontent_' + encodeURIComponent("{{ fallback_path }}"));
         if (!remember_password) {
             return null
         }
CoinK0in commented 2 years ago

I did some tests and when i use a subdirectory in the root url the fallback works for me. The URL always contains a first "/", regardless of the following subdirectories, this plugin creates a default key using "/" (Becomes %2F once URL encoded). And so the global password is correctly found and used.

Your problem seems to be related to the use of the same FQDN to host 2 different sites, with common paths, but with different global passwords. Which leads to overriding the value of one or other of your sites each time the global password is set. Problem that is not present on classic passwords, because the key created in the storage is more specific (Contains part of the URL of the page).

Your problem will be exactly the same with the search index, since by default mkdocs creates a "search_index.json" file related to your site's URL. So this file will also be overwritten depending on the browsing page (No global search for both sites at the same time). And so the problem will also be present on my search index management function...

We could theoretically parse the "site_url" or "site_dir" configuration variable to add a base path in the default password keyname, but in your examples both sites have the same base path and no "site_dir" directive.

Besides, I wonder how do you manage to host this configuration ?

On my side, I had to modify the "site_url" of you'r configurations, to finally have a hosting configuration (nginx) such as:

server {
    listen 80;
    server_name test.coink0in.local;
    location / {
        root /var/www/site/root/;
    }
    location /members {
        root /var/www/site/members/;
    }
}

The 2 "site_url" directives look like this now :

site_url: http://test.coink0in.local/
site_url: http://test.coink0in.local/members

And with this configuration I have the fallback problem described above. Due to global password variable overload. Same thing if I add a "subdir" subdirectory. /subdir/ (main root path) and /subdir/members (Area members) with nginx configuration changes (of course). Even if i use site_dir directive for specify the subdir part.

In this case of configuration with your code, I get the desired effect by using only the parsing of the "site_url" variable (if it is different between the two sites). But your fallback variable brings an advantage to force configure the variable.

I merge your PR on my side. The time to see if I find a better solution for reloading the script and a method to allow modification of the HTML template. Then I will push version 2.3.0

unverbuggt commented 2 years ago

Hi,

in the "encryptcontent_global_password_site_url.zip" from above I set site_url: http://localhost/foo. If you run mkdocs serve and the open up a browser at http://localhost it will redirect to http://localhost/foo. If you run mkdocs build the site/ subdirectory won't contain a foo folder, because you need to upload it somewhere where it is served at https://example.com/foo. The remember_password fallback will look for "/", but won't find anything there because the password entered at the site root will be saved at "/foo/".

CoinK0in commented 2 years ago

The remember_password fallback will look for "/", but won't find anything there because the password entered at the site root will be saved at "/foo/".

I don't understand that. It doesn't matter what page or part of your site you are on. In the password field, if you enter a password and use "CTRL+ENTER" to send the password (instead of classic ENTER), it is necessarily created in storage with the key encryptcontent_%2F. And so it will necessarily be the default password found by the global password fallback search performed on "/".

unverbuggt commented 2 years ago

if the site is not hostet at "/" of the "example.com", then the password you entered at "/foo/" wil be saved at encryptcontent_%2Ffoo%2F and not encryptcontent_%2F. so if you browse the site at "/foo/bar" and the "/foo/bar" page shares the same password as "/foo/" I'd like that it at least tries the password that was saved as encryptcontent_%2Ffoo%2F...

CoinK0in commented 2 years ago

if the site is not hostet at "/" of the "example.com", then the password you entered at "/foo/" wil be saved at encryptcontent_%2Ffoo%2F and not encryptcontent_%2F. so if you browse the site at "/foo/bar" and the "/foo/bar" page shares the same password as "/foo/" I'd like that it at least tries the password that was saved as encryptcontent_%2Ffoo%2F...

I'm not agree, that's not what I'm seeing on my side ..


I'm using python 3.10.4, with the following packages

mkdocs                       1.3.1
mkdocs-encryptcontent-plugin 2.3.0
mkdocs-material              8.4.2
mkdocs-material-extensions   1.0.3

My mkdocs.yml configuration file looks like this:

site_name: PoC_encryptcontent_plugins
site_url: http://coink0in.github.io/subdir/
site_dir: subdir/
theme:
  name: material
  custom_dir: my_theme_customizations/
  features:
    - navigation.tabs
plugins:
  - search: {}
  - encryptcontent:
      global_password: "321"
      remember_password: True
      session_storage: True
      password_button: True
      password_button_text: 'TESTME'
      encrypted_something:
        mkdocs-encrypted-toc: [nav, class]
nav:
  - Home: index.md
  - Tests:
    - Protection: 
      - No Password: "Tests/0-no_password.md"
      - Blank Password: "Tests/1-blank_password.md"
      - With Password: "Tests/2-with_password.md"
      - Diff Password: "Tests/3-diff_password.md"

Each file looks like this :

# index.md
PoC
# 0-no_password.md
PoC
password: 
# 1-blank_password.md
PoC
password: 321
# 2-with_password.md
PoC
password: 654
# 3-diff_password.md
PoC

I'm browsing my site for the first time and when I enter the URL, I'm redirected to /subdir/ which is the root.

WARNING - [xx:xx:xx] "GET / HTTP/1.1" code 302
DEBUG - [xx:xx:xx] "GET /subdir/HTTP/1.1" code 200

I going to the index.md page which has no default password. But since there is a global password, it is used to protect this page. When I enter a password 321 then use the [ENTER] key or the TESTME button, the page is correctly decrypted and a key is created in the SessionStorage with encryptcontent_%2Fsubdir%2F and as value the password 321 (+ the expires which is useless in session).

Then if I navigate on my site in the Tests tab, no page is decrypted by default, except the subdir/Tests/1-blank_password/ page since it has an empty password by default which prevents encryption. For all other pages the remember password function does not decrypt the content.

Next step (cf: .gif)

If now, I opened a new tab, I'm at the encrypted index page, then I enter my global password 321 and use the [CTRL]+[ENTER] keys to send the password (Does not work with the button), the page is correctly decrypted and a key is created in the SessionStorage with encryptcontent_%2F and as value the password 321 (+ the expires which is not used for nothing in session).

Then if I browse my site in the Tests tab, all the pages with the same password are decrypted.

If I navigate to the 3-diff_password page it is not decrypted because it does not share the same password (654). But if I enter the password 654 on this page, then it is correctly decrypted.

encryptcontent_demo_global_password


Could you explicitly describe the actions you do and your configuration, to never get the key encryptcontent_%2F ?

Do you only use the button to decrypt the pages ? Cause this button cannot be used as a global password is described in the documentation here.

unverbuggt commented 2 years ago

Hi,

I use Firefox 91.13.0esr. I don't use a button.

You're right: if I press Ctrl+Enter then it is saved as encryptcontent_%2F. But if I just press Enter then it is saved as encryptcontent_%2Fdigistarch%2F. According to your documentation this is intentional ("If remember password feature is activated, use button to decrypt generate a 'relative' key on your local storage"), the button does the same as just pressing Enter...

What was the reason to require Ctrl+Enter if a global password was used? I don't think this is very intuitive, as users who access the encrypted content might not dive in to the documentation of this plugin.

I'm closing the request. Maybe not-requiring Ctrl+Enter (Just Enter) in case of global_password + remember_password is a better approach.

CoinK0in commented 2 years ago

Of course, users are not going to read the documentation for this plugin. Maybe i can add this information in the default placeholder of the password field ..

The main reason for CTRL+ENTER is to identify the nature of the password used and to be able to create the corresponding entry. Without this distinction, when a user enter a password, how do we know if it is global or not ? And if the password is global, if the user navigate to another page with a different password, when he enters this new password, I overwrite the previous one ?

And same problem if we use the location to define the global password. No longer possible to enter from any page, you will have to go to the /members page and enter the password there, so that it is "global" ? And if I try, on each page to use the passwords stored with a common root path, in addition to making the process slow, there will be side effects such as the permanent display of the password error message.

Personally, I don't see how to do this, while retaining both the possibility of entering the global password from everywhere and entering specific passwords. But I am open to suggestions, if you have an idea it is welcome.