Skip to content

Commit

Permalink
Merge pull request #135
Browse files Browse the repository at this point in the history
feat(fastuser): support ldap auth
  • Loading branch information
lihuacai168 committed Oct 22, 2023
2 parents a2eae4a + 28ec2f2 commit 46a5f8d
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 73 deletions.
39 changes: 39 additions & 0 deletions FasterRunner/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"rest_framework_swagger",
"drf_yasg",
"system",
"django_auth_ldap"
]

MIDDLEWARE = [
Expand Down Expand Up @@ -309,6 +310,16 @@
"level": "INFO",
"propagate": True,
},
"fastuser": {
"handlers": ["default", "console", "error", "db"],
"level": "INFO",
"propagate": True,
},
"django_auth_ldap": {
"handlers": ["default", "console", "error", "db"],
"level": "INFO",
"propagate": True,
},
},
}
LOG_REQUEST_ID_HEADER = "HTTP_X_REQUEST_ID"
Expand All @@ -327,3 +338,31 @@
EMAIL_HOST_USER = os.environ.get("EMAIL_HOST_USER") # 配置邮箱
EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD") # 对应的授权码
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER



# LDAP配置
import ldap
from django_auth_ldap.config import LDAPSearch

AUTHENTICATION_BACKENDS = (
'django_auth_ldap.backend.LDAPBackend',
)

USE_LDAP = False # 如果需要开启LDAP认证,就设置位True
AUTH_LDAP_SERVER_URI = "ldap://localhost:389" # LDAP服务器地址,默认端口389

AUTH_LDAP_BIND_DN = "cn=admin,dc=myorg,dc=com" # LDAP管理员账号
AUTH_LDAP_BIND_PASSWORD = "admin" # LDAP管理员密码
AUTH_LDAP_USER_SEARCH = LDAPSearch(
"ou=Tester,dc=myorg,dc=com",
ldap.SCOPE_SUBTREE,
"(uid=%(user)s)",
) # LDAP搜索账号,ou可以理解为组织单位或者部门,不填写也是ok,dc可以理解为域名

AUTH_LDAP_USER_ATTR_MAP = {
"username": "uid",
"first_name": "givenName",
"last_name": "sn",
"email": "mail",
}
4 changes: 2 additions & 2 deletions fastuser/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ def create(self, validated_data):
return models.UserInfo.objects.create(**validated_data)


class UserLoginSerialzer(serializers.Serializer):
class UserLoginSerializer(serializers.Serializer):
username = serializers.CharField(required=True)
password = serializers.CharField(required=True)
password = serializers.CharField(required=True, min_length=6)


class UserModelSerializer(serializers.ModelSerializer):
Expand Down
150 changes: 80 additions & 70 deletions fastuser/views.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
from django.contrib.auth import get_user_model
from django.contrib.auth.hashers import make_password, check_password
from rest_framework_jwt.settings import api_settings
import logging
from typing import Optional

from django.conf import settings
from django.contrib.auth import authenticate, get_user_model
from django.contrib.auth.hashers import check_password, make_password
from drf_yasg.utils import swagger_auto_schema
from rest_framework.response import Response
from rest_framework.views import APIView
from drf_yasg.utils import swagger_auto_schema


from rest_framework_jwt.settings import api_settings

from fastuser import models, serializers
from fastuser.common import response
from fastuser import models
from fastuser import serializers

from fastuser.common.token import generate_token
from django.core.exceptions import ObjectDoesNotExist
from django.conf import settings

from fastuser.serializers import UserLoginSerializer

# 获取用户模型
User = get_user_model()

logger = logging.getLogger(__name__)


class RegisterView(APIView):
authentication_classes = ()
Expand Down Expand Up @@ -58,74 +57,85 @@ def post(self, request):
return Response(response.SYSTEM_ERROR)


def ldap_auth(username: str, password: str) -> Optional[User]:
ldap_user = authenticate(username=username, password=password)
if ldap_user and ldap_user.backend == "django_auth_ldap.backend.LDAPBackend":
logger.info(f"LDAP authentication successful for {username}")
local_user: User = User.objects.filter(username=username).first()
if local_user:
local_user.password = make_password(password)
local_user.save(update_fields=["password"])
logger.info(f"ldap认证通过,更新本地用户密码: {username}")
return local_user
logger.info(f"LDAP authentication failed for {username}")
return None


def local_auth(username: str, password: str) -> Optional[User]:
local_user = User.objects.filter(username=username).first()
if not local_user:
logger.warning(f"Local user does not exist: {username}")
return None
if local_user.is_active == 0:
logger.warning(f"Local user is blocked: {username}")
return None
if not check_password(password, local_user.password):
logger.warning(f"Local authentication failed: {username}")
return None
return local_user


def generate_token_and_respond(local_user: User):
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
payload = jwt_payload_handler(local_user)
token = jwt_encode_handler(payload)
response.LOGIN_SUCCESS["token"] = token
response.LOGIN_SUCCESS["user"] = local_user.username
response.LOGIN_SUCCESS["is_superuser"] = local_user.is_superuser
response.LOGIN_SUCCESS["show_hosts"] = local_user.show_hosts
return Response(response.LOGIN_SUCCESS)


class LoginView(APIView):
"""
登陆视图,用户名与密码匹配返回token
"""

authentication_classes = ()
permission_classes = ()

@swagger_auto_schema(request_body=serializers.UserLoginSerialzer)
@swagger_auto_schema(request_body=UserLoginSerializer)
def post(self, request):
"""
用户名密码一致返回token
{
username: str
password: str
}
"""
try:
username = request.data["username"]
password = request.data["password"]
except KeyError:
return Response(response.KEY_MISS)

user = User.objects.filter(username=username).first()

if not user:
return Response(response.USER_NOT_EXISTS)

if user.is_active == 0:
return Response(response.USER_BLOCKED)

if not check_password(password, user.password):
return Response(response.LOGIN_FAILED)

# token = generate_token(username)

# try:
# models.UserToken.objects.update_or_create(user=user, defaults={"token": token})
# except ObjectDoesNotExist:
# return Response(response.SYSTEM_ERROR)
# else:
# from rest_framework_jwt.settings import api_settings
#
# jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
# jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
#
# payload = jwt_payload_handler(user)
# token = jwt_encode_handler(payload)
# response.LOGIN_SUCCESS["token"] = token
# response.LOGIN_SUCCESS["user"] = username
# return Response(response.LOGIN_SUCCESS)
#
#

jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
response.LOGIN_SUCCESS["token"] = token
response.LOGIN_SUCCESS["user"] = username
response.LOGIN_SUCCESS["is_superuser"] = user.is_superuser
response.LOGIN_SUCCESS["show_hosts"] = user.show_hosts
return Response(response.LOGIN_SUCCESS)
serializer = UserLoginSerializer(data=request.data)

if serializer.is_valid():
username: str = serializer.validated_data["username"]
password: str = serializer.validated_data["password"]
masked_password = f"{password[0]}{'*' * (len(password) - 2)}{password[-1]}"
logger.info(f"Received login request for {username=}, password={masked_password}")

local_user = None
if settings.USE_LDAP:
logger.info(f"Attempting LDAP authentication for {username=}")
local_user = ldap_auth(username, password)

if not local_user:
logger.info(
f"LDAP authentication failed or not enabled, falling back to local authentication for {username=}")
local_user = local_auth(username, password)

if local_user:
logger.info(f"Authentication successful for {username=}")
return generate_token_and_respond(local_user)
else:
logger.info(f"Authentication failed for {username=}")
return Response(response.LOGIN_FAILED)
else:
return Response(serializer.errors)

class UserView(APIView):

def get(self, request):
users = User.objects.filter(is_active=1)
ser = serializers.UserModelSerializer(instance=users, many=True)
return Response(ser.data)
return Response(ser.data)
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,5 @@ sentry-sdk~=1.5.8
croniter~=1.3.5 # 计算下一次crontab
django-log-request-id~=2.0.0
gevent==22.10.2
django-filter~=2.4.0
django-filter~=2.4.0
django-auth-ldap==2.3.0

0 comments on commit 46a5f8d

Please sign in to comment.