Compare commits
23 commits
Author | SHA1 | Date | |
---|---|---|---|
|
6ba71545a1 | ||
|
ae3c156df7 | ||
|
1efe6ee078 | ||
|
9703eb6538 | ||
|
273a1acf01 | ||
|
3f37fe4826 | ||
79458d54cb | |||
|
0301e1a7e8 | ||
|
2f2d0c592e | ||
9e3aad1316 | |||
7a581e8357 | |||
84afaaa56d | |||
|
d38b5378b0 | ||
|
1b4107306b | ||
|
d598b9584e | ||
|
636b3d3052 | ||
|
fd0f0b56bd | ||
|
e45e5989db | ||
|
5890d95c59 | ||
|
27b880ef77 | ||
|
27ba06ce26 | ||
|
1a54de525b | ||
|
97b612e626 |
10 changed files with 166 additions and 83 deletions
4
.env.sample
Normal file
4
.env.sample
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
SECRET_KEY=ldskjflkdsnejnjsdnf
|
||||||
|
DEBUG=False
|
||||||
|
ENABLE_DEBUG_LOG=True
|
||||||
|
ALLOWED_HOSTS=localhost,.ungleich.ch
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,3 +1,6 @@
|
||||||
|
.idea/
|
||||||
venv/
|
venv/
|
||||||
db.sqlite3
|
db.sqlite3
|
||||||
aux/
|
aux/
|
||||||
|
__pycache__/
|
||||||
|
static/
|
||||||
|
|
112
README.md
112
README.md
|
@ -18,63 +18,57 @@ Related documentation:
|
||||||
|
|
||||||
## Overview ##
|
## Overview ##
|
||||||
|
|
||||||
This repository the reference implementation of the ungleichotp
|
This repository the reference implementation of the ungleichotp server.
|
||||||
server.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Using the ungleichotpclient ##
|
## Using the ungleichotpclient ##
|
||||||
|
|
||||||
|
The client can be used to test the ungleich-otp-server.
|
||||||
|
|
||||||
|
All client commands need the parameters --auth-name and --auth-realm.
|
||||||
|
Also either --auth-seed or --auth-token needs to be specified.
|
||||||
```
|
```
|
||||||
python manage.py ungleichotpclient create \
|
python manage.py ungleichotpclient create \
|
||||||
--server-url https://otp.ungleich.ch/ungleichotp/
|
--server-url https://otp.ungleich.ch/ungleichotp/
|
||||||
--name admin
|
--auth-name admin
|
||||||
--realm ungleich-admin
|
--auth-realm ungleich-admin
|
||||||
--seed AVALIDSEED
|
[--auth-seed THESEEDFORADMIN]
|
||||||
|
[--auth-token THECURRENTTOKEN]
|
||||||
```
|
```
|
||||||
|
|
||||||
Assuming you want to verify
|
### Creating new users
|
||||||
(name=ipv6only, realm=ungleich-intern, token=498593) is a
|
|
||||||
valid triple and you do have credentials to access ungleich-otp
|
|
||||||
(name=info@ungleich.ch, realm=ungleich-admin, seed=PZKBPTHDGSLZBKIZ),
|
|
||||||
then the following call will verify the token:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
UNGLEICHOTPNAME=info@ungleich.ch \
|
--name USERNAME --realm REALMOFUSER create
|
||||||
UNGLEICHOTPREALM=ungleich-admin \
|
```
|
||||||
UNGLEICHOTPSEED=PZKBPTHDGSLZBKIZ \
|
|
||||||
UNGLEICHOTPSERVER=http://localhost:8000/ungleichotp/verify/ \
|
The seed is randomly created.
|
||||||
python ungleichotpclient.py -n -r ungleich --token 498593
|
|
||||||
|
### Listing users
|
||||||
|
|
||||||
|
```
|
||||||
|
list
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deleting users
|
||||||
|
|
||||||
|
```
|
||||||
|
--name USERNAME --realm REALMOFUSER delete
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Verifying a token is correct
|
||||||
|
|
||||||
|
Verify using:
|
||||||
|
|
||||||
|
```
|
||||||
|
--name USERNAME --realm REALMOFUSER --token TOKENTOBEVERIFIED verify
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also verify using a seed:
|
You can also verify using a seed:
|
||||||
|
|
||||||
```
|
```
|
||||||
UNGLEICHOTPNAME=info@ungleich.ch \
|
--name USERNAME --realm REALMOFUSER --seed SEEDOFUSER verify
|
||||||
UNGLEICHOTPREALM=ungleich-admin \
|
|
||||||
UNGLEICHOTPSEED=PZKBPTHDGSLZBKIZ \
|
|
||||||
UNGLEICHOTPSERVER=http://localhost:8000/ungleichotp/verify/ \
|
|
||||||
python ungleichotpclient.py -n -r ungleich --seed CEKXVG3235PO2HDW
|
|
||||||
```
|
|
||||||
|
|
||||||
The client requires pyotp.
|
|
||||||
|
|
||||||
## Sample 2018-12-30
|
|
||||||
|
|
||||||
create:
|
|
||||||
(venv) [23:07] line:ungleich-otp% python manage.py ungleichotpclient create --server-url http://localhost:8000/ungleichotp/ --auth-name info@ungleich.ch --auth-realm ungleich-admin --auth-seed PZKBPTHDGSLZBKIZ --name nico$(date +%s) --realm ungleich-admin
|
|
||||||
|
|
||||||
verify:
|
|
||||||
|
|
||||||
```
|
|
||||||
(venv) [23:07] line:ungleich-otp% python manage.py ungleichotpclient verify --server-url http://localhost:8000/ungleichotp/ --auth-name info@ungleich.ch --auth-realm ungleich-admin --auth-seed PZKBPTHDGSLZBKIZ --name nico1546206660 --realm ungleich-admin --seed IXTARIU4H2F574M3
|
|
||||||
```
|
|
||||||
|
|
||||||
list:
|
|
||||||
|
|
||||||
```
|
|
||||||
(venv) [23:14] line:ungleich-otp% python manage.py ungleichotpclient list --server-url http://localhost:8000/ungleichotp/ --auth-name info@ungleich.ch --auth-realm ungleich-admin --auth-seed PZKBPTHDGSLZBKIZ
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@ -106,13 +100,13 @@ All micro services that are trusted to authenticate another micro
|
||||||
service should have an entry in the ungleich-auth realm, which allows
|
service should have an entry in the ungleich-auth realm, which allows
|
||||||
them to verify a token of somebody else.
|
them to verify a token of somebody else.
|
||||||
|
|
||||||
|
```
|
||||||
| Name | Capabilities |
|
| Name | Capabilities |
|
||||||
|------------------+--------------------------------------------|
|
|------------------+--------------------------------------------|
|
||||||
| ungleich-admin | authenticate, create, delete, list, update |
|
| ungleich-admin | authenticate, create, delete, list, update |
|
||||||
| ungleich-auth | authenticate |
|
| ungleich-auth | authenticate, verify |
|
||||||
| all other realms | NO ACCESS |
|
| all other realms | authenticate |
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Verify using http POST ##
|
## Verify using http POST ##
|
||||||
|
@ -124,12 +118,12 @@ Request JSON object:
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
name: "your-name",
|
auth_name: "auth-name",
|
||||||
realm: "your-realm",
|
auth_realm: "auth-realm",
|
||||||
token: "current time based token",
|
auth_token: "current time based token",
|
||||||
verifyname: "name that wants to be authenticated",
|
name: "name that wants to be authenticated",
|
||||||
verifyrealm: "realm that wants to be authenticated",
|
realm: "realm that wants to be authenticated",
|
||||||
verifytoken: "token that wants to be authenticated",
|
token: "token that wants to be authenticated"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -166,8 +160,8 @@ your application.
|
||||||
|
|
||||||
## Limitations ##
|
## Limitations ##
|
||||||
|
|
||||||
* Name, Realm and seed are hard coded to 128 bytes length. This can be
|
* Name, Realm and seed are hard coded to 128 bytes length.
|
||||||
changed, if necessary.
|
This can be changed, if necessary.
|
||||||
* Only python3 support for ungleichotp
|
* Only python3 support for ungleichotp
|
||||||
|
|
||||||
|
|
||||||
|
@ -192,8 +186,8 @@ your application.
|
||||||
- [x] (server) Implement creating new "User" by POST / Model based
|
- [x] (server) Implement creating new "User" by POST / Model based
|
||||||
- [n] (server) Remove hard coded JSON in /verify (no - good enough for the moment)
|
- [n] (server) Remove hard coded JSON in /verify (no - good enough for the moment)
|
||||||
- [x] (server) Fully rename server from ungleichotp to ungleichotpserver
|
- [x] (server) Fully rename server from ungleichotp to ungleichotpserver
|
||||||
- [ ] (security) Ensure that only the right realms can verify
|
- [x] (security) Ensure that only the right realms can verify
|
||||||
- [ ] (security) Ensure that only the right realms can manage
|
- [x] (security) Ensure that only the right realms can manage
|
||||||
- [ ] (doc) Add proper documentation
|
- [ ] (doc) Add proper documentation
|
||||||
- [ ] (server) Add tests for verify
|
- [ ] (server) Add tests for verify
|
||||||
- [ ] (server) Add tests for authentication
|
- [ ] (server) Add tests for authentication
|
||||||
|
@ -206,14 +200,18 @@ your application.
|
||||||
- [ ] (client) Bootstrap Django + DRF (including an object for CRUD)
|
- [ ] (client) Bootstrap Django + DRF (including an object for CRUD)
|
||||||
- [ ] (client) Add custom authentication / remote auth
|
- [ ] (client) Add custom authentication / remote auth
|
||||||
- [ ] (client) Show case: any realm vs. specific realm
|
- [ ] (client) Show case: any realm vs. specific realm
|
||||||
- [ ] (library) Write a "client library" that can use ungleichotp
|
- [x] (library) Write a "client library" that can use ungleichotp
|
||||||
- [ ] (library) extract generic parts from server
|
- [x] (library) extract generic parts from server
|
||||||
- [ ] (library) upload to pypi
|
- [ ] (library) upload to pypi
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
### 0.8, 2019-02-08
|
||||||
|
|
||||||
|
* Verify needed to call super()
|
||||||
|
|
||||||
### 0.6, 2018-11-18
|
### 0.6, 2018-11-18
|
||||||
|
|
||||||
* Reuse TokenSerializer for VerifySerializer logic
|
* Reuse TokenSerializer for VerifySerializer logic
|
||||||
|
|
2
logs/.gitignore
vendored
Normal file
2
logs/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
*
|
||||||
|
!.gitignore
|
|
@ -14,16 +14,16 @@ class Command(BaseCommand):
|
||||||
parser.add_argument('--server-url', required=True)
|
parser.add_argument('--server-url', required=True)
|
||||||
|
|
||||||
# For creating / verifying
|
# For creating / verifying
|
||||||
parser.add_argument('--name')
|
parser.add_argument('--name', help="Name to create/verify")
|
||||||
parser.add_argument('--realm')
|
parser.add_argument('--realm', help="Realm for create/verify")
|
||||||
parser.add_argument('--token')
|
parser.add_argument('--token', help="Token for create/verify")
|
||||||
parser.add_argument('--seed')
|
parser.add_argument('--seed', help="Seed for create/verify")
|
||||||
|
|
||||||
# How to authenticate against ungleich-otp
|
# How to authenticate against ungleich-otp
|
||||||
parser.add_argument('--auth-name', required=True)
|
parser.add_argument('--auth-name', required=True, help="Name for auth")
|
||||||
parser.add_argument('--auth-realm', required=True)
|
parser.add_argument('--auth-realm', required=True, help="Realm for auth")
|
||||||
parser.add_argument('--auth-token')
|
parser.add_argument('--auth-token', help="Token for auth")
|
||||||
parser.add_argument('--auth-seed')
|
parser.add_argument('--auth-seed', help="Seed for auth")
|
||||||
|
|
||||||
parser.add_argument('command', choices=['create',
|
parser.add_argument('command', choices=['create',
|
||||||
'delete',
|
'delete',
|
||||||
|
|
|
@ -2,6 +2,11 @@ from django.db import models
|
||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
from rest_framework import exceptions
|
from rest_framework import exceptions
|
||||||
from rest_framework import authentication
|
from rest_framework import authentication
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import pyotp
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class OTPSeed(AbstractUser):
|
class OTPSeed(AbstractUser):
|
||||||
|
@ -17,7 +22,13 @@ class OTPSeed(AbstractUser):
|
||||||
"""
|
"""
|
||||||
inject username to ensure it stays unique / is setup at all
|
inject username to ensure it stays unique / is setup at all
|
||||||
"""
|
"""
|
||||||
self.username = "{}@{}".format(self.name, self.realm)
|
if not self.is_superuser:
|
||||||
|
self.username = "{}@{}".format(self.name, self.realm)
|
||||||
|
else:
|
||||||
|
self.name = self.username
|
||||||
|
self.realm = "ungleich-admin"
|
||||||
|
self.seed = pyotp.random_base32()
|
||||||
|
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -27,19 +38,20 @@ from otpauth.serializer import TokenSerializer
|
||||||
|
|
||||||
class OTPAuthentication(authentication.BaseAuthentication):
|
class OTPAuthentication(authentication.BaseAuthentication):
|
||||||
def authenticate(self, request):
|
def authenticate(self, request):
|
||||||
|
logger.debug("in authenticate {}".format(json.dumps(request.data)))
|
||||||
serializer = TokenSerializer(data=request.data)
|
serializer = TokenSerializer(data=request.data)
|
||||||
|
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
print("trying to save... {}".format(serializer))
|
|
||||||
instance, token = serializer.save()
|
instance, token = serializer.save()
|
||||||
else:
|
else:
|
||||||
print("Invalide serialize,")
|
logger.error("serializer is invalid")
|
||||||
raise exceptions.AuthenticationFailed()
|
raise exceptions.AuthenticationFailed()
|
||||||
|
|
||||||
# not dealing with admin realm -> can only be auth [see serializer]
|
# not dealing with admin realm -> can only be auth [see serializer]
|
||||||
if not instance.realm == "ungleich-admin":
|
if not instance.realm == "ungleich-admin":
|
||||||
if not request.path == "/ungleichotp/verify/":
|
if not request.path == "/ungleichotp/verify/":
|
||||||
|
logger.debug("request.path is not /ungleichotp/verify/")
|
||||||
raise exceptions.AuthenticationFailed()
|
raise exceptions.AuthenticationFailed()
|
||||||
|
|
||||||
print("AUTH DONE: {} - {}".format(request.path, instance))
|
logger.debug("AUTH DONE: {} - {}".format(request.path, instance))
|
||||||
return (instance, token)
|
return (instance, token)
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
|
import logging
|
||||||
import pyotp
|
import pyotp
|
||||||
import otpauth
|
import otpauth
|
||||||
from rest_framework import serializers, exceptions
|
from rest_framework import serializers, exceptions
|
||||||
from otpauth.models import OTPSeed
|
from otpauth.models import OTPSeed
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# For accessing / modifying the data -- currently unused
|
# For accessing / modifying the data -- currently unused
|
||||||
class OTPSerializer(serializers.ModelSerializer):
|
class OTPSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -31,22 +34,34 @@ class TokenSerializer(serializers.Serializer):
|
||||||
auth_realm = self.validated_data.get(self.realm_name)
|
auth_realm = self.validated_data.get(self.realm_name)
|
||||||
|
|
||||||
# only 2 special realms can login
|
# only 2 special realms can login
|
||||||
if not auth_realm in ["ungleich-admin", "ungleich-auth" ]:
|
# if not auth_realm in ["ungleich-admin", "ungleich-auth" ]:
|
||||||
raise exceptions.AuthenticationFailed()
|
# logger.error("Auth-realm is neither ungleich-admin "
|
||||||
|
# "nor ungleich-auth".format()
|
||||||
|
# )
|
||||||
|
# raise exceptions.AuthenticationFailed()
|
||||||
|
|
||||||
print("auth: [{}]{}@'{}' {} + {})".format(self.name_name, auth_name, auth_realm, auth_token, self.validated_data))
|
logger.debug("auth: [{}]{}@'{}' {} + {})".format(
|
||||||
|
self.name_name, auth_name, auth_realm,
|
||||||
|
auth_token, self.validated_data
|
||||||
|
))
|
||||||
|
|
||||||
# 1. Verify that the connection might authenticate
|
# 1. Verify that the connection might authenticate
|
||||||
try:
|
try:
|
||||||
|
logger.debug("Checking in db for name:{} & realm:{}".format(
|
||||||
|
auth_name, auth_realm
|
||||||
|
))
|
||||||
db_instance = otpauth.models.OTPSeed.objects.get(name=auth_name, realm=auth_realm)
|
db_instance = otpauth.models.OTPSeed.objects.get(name=auth_name, realm=auth_realm)
|
||||||
except (OTPSeed.MultipleObjectsReturned, OTPSeed.DoesNotExist):
|
except (OTPSeed.MultipleObjectsReturned, OTPSeed.DoesNotExist):
|
||||||
print("does not exist")
|
logger.error("OTPSeed name: {}, realm: {} does not exist".format(
|
||||||
|
auth_name, auth_realm
|
||||||
|
))
|
||||||
raise exceptions.AuthenticationFailed()
|
raise exceptions.AuthenticationFailed()
|
||||||
|
logger.debug("Found seed: {}".format(db_instance.seed))
|
||||||
totp = pyotp.TOTP(db_instance.seed)
|
totp = pyotp.TOTP(db_instance.seed)
|
||||||
print("calculated token = {}".format(totp.now()))
|
logger.debug("calculated token = {}".format(totp.now()))
|
||||||
|
|
||||||
if not totp.verify(auth_token, valid_window=3):
|
if not totp.verify(auth_token, valid_window=3):
|
||||||
|
logger.error("totp not verified")
|
||||||
raise exceptions.AuthenticationFailed()
|
raise exceptions.AuthenticationFailed()
|
||||||
|
|
||||||
return (db_instance, auth_token)
|
return (db_instance, auth_token)
|
||||||
|
@ -65,4 +80,8 @@ class VerifySerializer(TokenSerializer):
|
||||||
auth_realm = self.validated_data.get("auth_realm")
|
auth_realm = self.validated_data.get("auth_realm")
|
||||||
|
|
||||||
if not auth_realm == "ungleich-auth":
|
if not auth_realm == "ungleich-auth":
|
||||||
|
logger.error("Auth-realm is not ungleich-auth")
|
||||||
raise exceptions.AuthenticationFailed()
|
raise exceptions.AuthenticationFailed()
|
||||||
|
|
||||||
|
# Do the authentication part
|
||||||
|
super().save()
|
||||||
|
|
|
@ -7,6 +7,11 @@ from rest_framework.response import Response
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
from otpauth.serializer import VerifySerializer, OTPSerializer, TokenSerializer
|
from otpauth.serializer import VerifySerializer, OTPSerializer, TokenSerializer
|
||||||
from otpauth.models import OTPSeed
|
from otpauth.models import OTPSeed
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class OTPVerifyViewSet(viewsets.ModelViewSet):
|
class OTPVerifyViewSet(viewsets.ModelViewSet):
|
||||||
serializer_class = OTPSerializer
|
serializer_class = OTPSerializer
|
||||||
|
@ -20,7 +25,7 @@ class OTPVerifyViewSet(viewsets.ModelViewSet):
|
||||||
Now we inspect the payload and return ok,
|
Now we inspect the payload and return ok,
|
||||||
if they also verify
|
if they also verify
|
||||||
"""
|
"""
|
||||||
|
logger.debug("in verify {}".format(json.dumps(request.data)))
|
||||||
serializer = VerifySerializer(data=request.data)
|
serializer = VerifySerializer(data=request.data)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
serializer.save()
|
serializer.save()
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
pyotp>=2.2.6
|
pyotp>=2.2.6
|
||||||
django>=2.1.2
|
django==2.2.16
|
||||||
djangorestframework
|
djangorestframework
|
||||||
|
python-decouple>=3.1
|
||||||
|
|
||||||
# DB
|
# DB
|
||||||
psycopg2
|
psycopg2>=2.8,<2.9
|
||||||
|
|
||||||
# Recommended
|
# Recommended
|
||||||
markdown
|
markdown
|
||||||
|
|
|
@ -10,6 +10,8 @@ For the full list of settings and their values, see
|
||||||
https://docs.djangoproject.com/en/2.1/ref/settings/
|
https://docs.djangoproject.com/en/2.1/ref/settings/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from decouple import config, Csv
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
|
@ -20,7 +22,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
# See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
SECRET_KEY = 'h^*!&u7yaac_6t02kk4de%$aagp6_j#+_wnw3@rqu6os0tlv#r'
|
SECRET_KEY = config('SECRET_KEY')
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
|
||||||
|
@ -129,10 +131,8 @@ DEBUG_DATABASES = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DEBUG = False
|
DEBUG = config('DEBUG', False, cast=bool)
|
||||||
ALLOWED_HOSTS = [
|
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='localhost', cast=Csv())
|
||||||
".ungleich.ch"
|
|
||||||
]
|
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
|
@ -146,6 +146,45 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
STATIC_ROOT = os.path.join(BASE_DIR, "static")
|
STATIC_ROOT = os.path.join(BASE_DIR, "static")
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = '/static/'
|
||||||
|
|
||||||
|
LOGGING = {
|
||||||
|
'disable_existing_loggers': False,
|
||||||
|
'version': 1,
|
||||||
|
'formatters': {
|
||||||
|
'standard': {
|
||||||
|
'format': '%(asctime)s %(levelname)s %(name)s: %(message)s'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'handlers': {
|
||||||
|
'default': {
|
||||||
|
'level': 'DEBUG',
|
||||||
|
'class': 'logging.handlers.RotatingFileHandler',
|
||||||
|
'filename': 'logs/debug.log',
|
||||||
|
'maxBytes': 1024*1024*5,
|
||||||
|
'backupCount': 10,
|
||||||
|
'formatter': 'standard',
|
||||||
|
},
|
||||||
|
'console': {
|
||||||
|
'class': 'logging.StreamHandler',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if config('ENABLE_DEBUG_LOG', cast=bool, default=False):
|
||||||
|
loggers_dict = {}
|
||||||
|
modules_to_log_list = config(
|
||||||
|
'MODULES_TO_LOG', default='django', cast=Csv()
|
||||||
|
)
|
||||||
|
for custom_module in modules_to_log_list:
|
||||||
|
logger_item = {
|
||||||
|
custom_module: {
|
||||||
|
'handlers': ['default'],
|
||||||
|
'level': 'DEBUG',
|
||||||
|
'propagate': True
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loggers_dict.update(logger_item)
|
||||||
|
|
||||||
|
LOGGING['loggers'] = loggers_dict
|
||||||
|
|
||||||
|
|
||||||
if "DEBUG" in os.environ:
|
if "DEBUG" in os.environ:
|
||||||
|
|
Loading…
Reference in a new issue