django-smart-dynamic-path 1.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_smart_dynamic_path/__init__.py +8 -0
- django_smart_dynamic_path/django_app.py +18 -0
- django_smart_dynamic_path/management/__init__.py +0 -0
- django_smart_dynamic_path/management/commands/__init__.py +0 -0
- django_smart_dynamic_path/management/commands/generate_secret_key.py +22 -0
- django_smart_dynamic_path/management/commands/get_admin_path.py +16 -0
- django_smart_dynamic_path/templatetags/__init__.py +0 -0
- django_smart_dynamic_path/templatetags/admin_link.py +11 -0
- django_smart_dynamic_path/urls.py +19 -0
- django_smart_dynamic_path-1.0.2.dist-info/METADATA +209 -0
- django_smart_dynamic_path-1.0.2.dist-info/RECORD +14 -0
- django_smart_dynamic_path-1.0.2.dist-info/WHEEL +5 -0
- django_smart_dynamic_path-1.0.2.dist-info/licenses/LICENSE +28 -0
- django_smart_dynamic_path-1.0.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Copyright (©) 2026, Alexander Suvorov. All rights reserved.
|
|
2
|
+
from django.conf import settings
|
|
3
|
+
from smart_dynamic_path import generate_path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_admin_path(period: str = "day") -> str:
|
|
7
|
+
secret_key = getattr(settings, 'SECRET_KEY', None)
|
|
8
|
+
if not secret_key:
|
|
9
|
+
raise ValueError("SECRET_KEY not found in settings")
|
|
10
|
+
|
|
11
|
+
if period is None:
|
|
12
|
+
period = getattr(settings, 'SECRET_ADMIN_PERIOD', 'day')
|
|
13
|
+
|
|
14
|
+
return generate_path(secret_key, period, prefix='')
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_admin_url(period: str = "day") -> str:
|
|
18
|
+
return f"/admin/{get_admin_path(period)}/"
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Copyright (©) 2026, Alexander Suvorov. All rights reserved.
|
|
2
|
+
from django.core.management.base import BaseCommand
|
|
3
|
+
from smart_dynamic_path import generate_secret_key
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Command(BaseCommand):
|
|
7
|
+
help = 'Generate SECRET_KEY from secret phrase'
|
|
8
|
+
|
|
9
|
+
def add_arguments(self, parser):
|
|
10
|
+
parser.add_argument('--secret', '-s', required=True, help='Secret phrase')
|
|
11
|
+
parser.add_argument('--period', '-p', default='day',
|
|
12
|
+
choices=['day', 'month', 'hour', 'static'],
|
|
13
|
+
help='Period for path generation (default: day)')
|
|
14
|
+
|
|
15
|
+
def handle(self, *args, **options):
|
|
16
|
+
secret = options['secret']
|
|
17
|
+
period = options['period']
|
|
18
|
+
|
|
19
|
+
secret_key = generate_secret_key(secret)
|
|
20
|
+
|
|
21
|
+
self.stdout.write(self.style.SUCCESS(f"SECRET_KEY={secret_key}"))
|
|
22
|
+
self.stdout.write(self.style.SUCCESS(f"SECRET_ADMIN_PERIOD={period}"))
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Copyright (©) 2026, Alexander Suvorov. All rights reserved.
|
|
2
|
+
from django.core.management.base import BaseCommand
|
|
3
|
+
from django_smart_dynamic_path.django_app import get_admin_path, get_admin_url
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Command(BaseCommand):
|
|
7
|
+
help = 'Get current secret admin path'
|
|
8
|
+
|
|
9
|
+
def add_arguments(self, parser):
|
|
10
|
+
parser.add_argument('--full', action='store_true', help='Show full URL')
|
|
11
|
+
|
|
12
|
+
def handle(self, *args, **options):
|
|
13
|
+
if options['full']:
|
|
14
|
+
self.stdout.write(self.style.SUCCESS(get_admin_url()))
|
|
15
|
+
else:
|
|
16
|
+
self.stdout.write(self.style.SUCCESS(f"/{get_admin_path()}/"))
|
|
File without changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Copyright (©) 2026, Alexander Suvorov. All rights reserved.
|
|
2
|
+
from django import template
|
|
3
|
+
from django_smart_dynamic_path.django_app import get_admin_url
|
|
4
|
+
|
|
5
|
+
register = template.Library()
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@register.simple_tag
|
|
9
|
+
def secret_admin_link():
|
|
10
|
+
"""Template tag to get current secret admin URL"""
|
|
11
|
+
return get_admin_url()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Copyright (©) 2026, Alexander Suvorov. All rights reserved.
|
|
2
|
+
"""
|
|
3
|
+
IMPORTANT: Due to Django's URL compilation mechanism,
|
|
4
|
+
the admin path is calculated once when the server starts.
|
|
5
|
+
To apply a new path when the period changes, restart the server.
|
|
6
|
+
|
|
7
|
+
Add to cron for automatic restart:
|
|
8
|
+
0 0 * * * systemctl restart gunicorn
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from django.urls import path
|
|
12
|
+
from django.contrib import admin
|
|
13
|
+
from django_smart_dynamic_path.django_app import get_admin_path
|
|
14
|
+
|
|
15
|
+
CURRENT_ADMIN_PATH = get_admin_path()
|
|
16
|
+
|
|
17
|
+
urlpatterns = [
|
|
18
|
+
path(f'{CURRENT_ADMIN_PATH}/', admin.site.urls),
|
|
19
|
+
]
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-smart-dynamic-path
|
|
3
|
+
Version: 1.0.2
|
|
4
|
+
Summary: Dynamic secret admin URL for Django. Time-based, deterministic, no storage. Hide your Django admin behind a URL that changes daily/monthly/hourly. Only you know the secret phrase to generate the current path.
|
|
5
|
+
Author-email: Alexander Suvorov <smartlegionlab@gmail.com>
|
|
6
|
+
License: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://github.com/smartlegionlab/django-smart-dynamic-path
|
|
8
|
+
Classifier: Framework :: Django
|
|
9
|
+
Classifier: Framework :: Django :: 3.2
|
|
10
|
+
Classifier: Framework :: Django :: 4.0
|
|
11
|
+
Classifier: Framework :: Django :: 5.0
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Classifier: Topic :: Security
|
|
21
|
+
Requires-Python: >=3.7
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: smart-dynamic-path>=1.0.1
|
|
25
|
+
Requires-Dist: Django>=3.2
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# Django Smart Dynamic Path <sup>v1.0.2</sup>
|
|
29
|
+
|
|
30
|
+
**Dynamic secret admin URL for Django. Time-based, deterministic, no storage.**
|
|
31
|
+
|
|
32
|
+
Hide your Django admin behind a URL that changes daily/monthly. Only you know the secret phrase to generate the current path.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
[](https://github.com/smartlegionlab/django-smart-dynamic-path/)
|
|
37
|
+

|
|
38
|
+
[](https://github.com/smartlegionlab/django-smart-dynamic-path/blob/master/LICENSE)
|
|
39
|
+
[](https://github.com/smartlegionlab/django-smart-dynamic-path/stargazers)
|
|
40
|
+
[](https://github.com/smartlegionlab/django-smart-dynamic-path/network/members)
|
|
41
|
+
|
|
42
|
+
[](https://pypi.org/project/django-smart-dynamic-path/)
|
|
43
|
+
[](https://pypi.org/project/django-smart-dynamic-path)
|
|
44
|
+
[](https://pypi.org/project/django-smart-dynamic-path)
|
|
45
|
+
[](https://pepy.tech/projects/django-smart-dynamic-path)
|
|
46
|
+
[](https://pepy.tech/projects/django-smart-dynamic-path)
|
|
47
|
+
[](https://pepy.tech/projects/django-smart-dynamic-path)
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## What it does
|
|
52
|
+
|
|
53
|
+
Standard Django admin: `/admin/` (always the same)
|
|
54
|
+
|
|
55
|
+
This package: `/admin/a1b2c3d4e5f6g7h8/` (changes automatically)
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Disclaimer
|
|
60
|
+
|
|
61
|
+
**By using this software, you agree to the full disclaimer terms.**
|
|
62
|
+
|
|
63
|
+
**Summary:** Software provided "AS IS" without warranty. You assume all risks.
|
|
64
|
+
|
|
65
|
+
**Full legal disclaimer:** See [DISCLAIMER.md](https://github.com/smartlegionlab/django-smart-dynamic-path/blob/master/DISCLAIMER.md)
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Installation
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install django-smart-dynamic-path
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Quick start
|
|
76
|
+
|
|
77
|
+
### 1. Add to `INSTALLED_APPS`
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
INSTALLED_APPS = [
|
|
81
|
+
'django_smart_dynamic_path',
|
|
82
|
+
'django.contrib.admin',
|
|
83
|
+
# ...
|
|
84
|
+
]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 2. Replace admin URLs in `urls.py`
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from django.urls import path, include
|
|
91
|
+
|
|
92
|
+
urlpatterns = [
|
|
93
|
+
path('admin/', include('django_smart_dynamic_path.urls')),
|
|
94
|
+
]
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 3. Generate SECRET_KEY from secret phrase (local)
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
smart-dynamic-path --secret "your secret phrase" --key-only
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Copy the output and set as `SECRET_KEY` in your `.env` or `settings.py`.
|
|
104
|
+
|
|
105
|
+
### 4. Configure period (optional, in `settings.py`)
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
SECRET_ADMIN_PERIOD = 'day' # day, month, static
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 5. Get current admin path (server)
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
python manage.py get_admin_path --full
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## ⚠️ Important Django limitation
|
|
118
|
+
|
|
119
|
+
Due to Django's URL compilation mechanism, the admin path is calculated **once when the server starts**.
|
|
120
|
+
To apply a new path when the period changes (e.g., next day), you need to restart the server.
|
|
121
|
+
|
|
122
|
+
### Automatic restart with cron
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# Add to crontab (restart at midnight)
|
|
126
|
+
0 0 * * * systemctl restart gunicorn
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
This is not a bug, but a Django architectural feature.
|
|
130
|
+
|
|
131
|
+
## Django management commands
|
|
132
|
+
|
|
133
|
+
| Command | Output |
|
|
134
|
+
|----------------------------------------------------------|-----------------------|
|
|
135
|
+
| `python manage.py generate_secret_key --secret "phrase"` | SECRET_KEY and period |
|
|
136
|
+
| `python manage.py get_admin_path` | `a1b2c3d4e5f6g7h8` |
|
|
137
|
+
| `python manage.py get_admin_path --full` | `/a1b2c3d4e5f6g7h8/` |
|
|
138
|
+
|
|
139
|
+
## Template tag
|
|
140
|
+
|
|
141
|
+
```django
|
|
142
|
+
{% load admin_link %}
|
|
143
|
+
<a href="{% secret_admin_link %}">Secret Admin</a>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Python API
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
from django_smart_dynamic_path import get_admin_path, get_admin_url
|
|
150
|
+
|
|
151
|
+
path = get_admin_path() # 'a1b2c3d4e5f6g7h8'
|
|
152
|
+
url = get_admin_url() # 'admin/a1b2c3d4e5f6g7h8/'
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## How it works
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
SECRET_KEY = SHA256(secret_phrase) # 64 hex chars (256 bits)
|
|
159
|
+
ADMIN_PATH = SHA256(SECRET_KEY + date)[:16].hex() # 32 hex chars (128 bits)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
- Secret phrase → SECRET_KEY (256 bits)
|
|
163
|
+
- SECRET_KEY + current date → admin path (128 bits)
|
|
164
|
+
- Path changes automatically based on period (day/month/hour/static)
|
|
165
|
+
- Same secret phrase always produces same SECRET_KEY
|
|
166
|
+
- Same SECRET_KEY + same date always produces same path
|
|
167
|
+
|
|
168
|
+
## Getting the path locally
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
git clone https://github.com/smartlegionlab/smart-dynamic-path
|
|
172
|
+
cd smart-dynamic-path
|
|
173
|
+
|
|
174
|
+
python3 -m smart_dynamic_path.cli --secret "my secret phrase"
|
|
175
|
+
python3 -m smart_dynamic_path.cli --secret "my secret phrase" --period month --prefix admin --full
|
|
176
|
+
python3 -m smart_dynamic_path.cli --secret "my secret phrase" --key-only
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Implemented paradigms
|
|
180
|
+
|
|
181
|
+
### 1. Pointer‑Based Security
|
|
182
|
+
The admin URL is not stored anywhere. It is regenerated on demand from a secret phrase and current time. There is no stored "pointer" — only the ability to compute it.
|
|
183
|
+
|
|
184
|
+
**DOI:** [10.5281/zenodo.17204738](https://doi.org/10.5281/zenodo.17204738)
|
|
185
|
+
|
|
186
|
+
### 2. Local Data Regeneration
|
|
187
|
+
The exact admin path is computed locally on the developer's machine using only the secret phrase and date, without accessing the server. The server never knows the secret phrase.
|
|
188
|
+
|
|
189
|
+
**DOI:** [10.5281/zenodo.17264327](https://doi.org/10.5281/zenodo.17264327)
|
|
190
|
+
|
|
191
|
+
### 3. Position‑Candidate‑Hypothesis (PCH)
|
|
192
|
+
Among all possible URL paths (2¹²⁸ candidates), only one specific path generated by the secret phrase is valid at any given time. The hypothesis (which path is valid) is verified through the hash function.
|
|
193
|
+
|
|
194
|
+
**DOI:** [10.5281/zenodo.17614888](https://doi.org/10.5281/zenodo.17614888)
|
|
195
|
+
|
|
196
|
+
## Security
|
|
197
|
+
|
|
198
|
+
- No additional secrets — uses only Django's existing SECRET_KEY
|
|
199
|
+
- Secret phrase exists only in memory, never stored
|
|
200
|
+
- 32 hex chars = 2¹²⁸ possible paths (no brute force)
|
|
201
|
+
- Time‑based rotation limits exposure window
|
|
202
|
+
|
|
203
|
+
## License
|
|
204
|
+
|
|
205
|
+
BSD-3-Clause
|
|
206
|
+
|
|
207
|
+
## Author
|
|
208
|
+
|
|
209
|
+
Alexander Suvorov [@smartlegionlab](https://github.com/smartlegionlab)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
django_smart_dynamic_path/__init__.py,sha256=TcqutXbiG5Cz7B9Nzvl-lhyEH8vcPNw-XwYRDEdiyAU,221
|
|
2
|
+
django_smart_dynamic_path/django_app.py,sha256=Aeeo5zrbYQ1QmkNrcT2W2UudozF4Rg3VC8uXmvn_QtA,572
|
|
3
|
+
django_smart_dynamic_path/urls.py,sha256=sPXGbE5NGmGaZ-uhbWLA6upEBCBiu3vbnLgF5_eNefw,559
|
|
4
|
+
django_smart_dynamic_path/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
django_smart_dynamic_path/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
django_smart_dynamic_path/management/commands/generate_secret_key.py,sha256=_gz2i3kp-myabN1AquX7PHtcgDb1h5l1IDf9nB-ehmw,895
|
|
7
|
+
django_smart_dynamic_path/management/commands/get_admin_path.py,sha256=8l9huWQFtPdqR_0tkqd94aowcmnF3pIz9tQR5yZFZU4,611
|
|
8
|
+
django_smart_dynamic_path/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
django_smart_dynamic_path/templatetags/admin_link.py,sha256=jUtlTOz2DN1s1UbuMW6VgSclNrR3GzjOJV6_dBaHj7A,315
|
|
10
|
+
django_smart_dynamic_path-1.0.2.dist-info/licenses/LICENSE,sha256=bXnWlOMydXEp3EEgmsJuPpErliZndYEC9RReI1H-Jek,1504
|
|
11
|
+
django_smart_dynamic_path-1.0.2.dist-info/METADATA,sha256=iJOkkL97U93TC5MsymjSG14ZyTklBPKa8gWaCYvHMY4,7649
|
|
12
|
+
django_smart_dynamic_path-1.0.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
13
|
+
django_smart_dynamic_path-1.0.2.dist-info/top_level.txt,sha256=SeOiM2DbxfeXvbc843uJ6hsT4mZ-PdgMkAxDYRW-UEg,26
|
|
14
|
+
django_smart_dynamic_path-1.0.2.dist-info/RECORD,,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Alexander Suvorov
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
django_smart_dynamic_path
|