This commit is contained in:
0
custom_auth/__init__.py
Normal file
0
custom_auth/__init__.py
Normal file
27
custom_auth/admin.py
Normal file
27
custom_auth/admin.py
Normal file
@ -0,0 +1,27 @@
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
from .models import CustomUser
|
||||
|
||||
# admin.site.register(CustomUser)
|
||||
class CustomUserAdmin(UserAdmin):
|
||||
"""사용자 관리자 페이지 커스터마이징"""
|
||||
model = CustomUser
|
||||
list_display = ('username', 'email', 'is_staff', 'is_active')
|
||||
list_filter = ('is_staff', 'is_active')
|
||||
|
||||
# 사용자 필드 구성
|
||||
fieldsets = (
|
||||
(None, {'fields': ('username', 'password')}),
|
||||
('Personal Info', {'fields': ('email', 'encrypted_private_key')}),
|
||||
('Permissions', {'fields': ('is_staff', 'is_active')}),
|
||||
)
|
||||
|
||||
# 읽기 전용 필드 추가
|
||||
readonly_fields = ('encrypted_private_key',)
|
||||
|
||||
search_fields = ('username', 'email')
|
||||
ordering = ('username',)
|
||||
|
||||
|
||||
# 관리자 등록
|
||||
admin.site.register(CustomUser, CustomUserAdmin)
|
6
custom_auth/apps.py
Normal file
6
custom_auth/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class CustomAuthConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'custom_auth'
|
67
custom_auth/forms.py
Normal file
67
custom_auth/forms.py
Normal file
@ -0,0 +1,67 @@
|
||||
from django import forms
|
||||
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
|
||||
from .models import CustomUser
|
||||
|
||||
|
||||
class CustomUserCreationForm(UserCreationForm):
|
||||
class Meta:
|
||||
model = CustomUser
|
||||
fields = ("username", "email", "grade") # 필요에 따라 추가 필드 지정
|
||||
|
||||
|
||||
class CustomUserChangeForm(UserChangeForm):
|
||||
password = forms.CharField(
|
||||
label="새 비밀번호",
|
||||
required=False,
|
||||
widget=forms.PasswordInput(attrs={"class": "form-control"}),
|
||||
)
|
||||
password_confirm = forms.CharField(
|
||||
label="비밀번호 확인",
|
||||
required=False,
|
||||
widget=forms.PasswordInput(attrs={"class": "form-control"}),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = CustomUser
|
||||
fields = ["email", "nhnc_id", "nhnc_api_tenant_id"]
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
password = cleaned_data.get("password")
|
||||
password_confirm = cleaned_data.get("password_confirm")
|
||||
|
||||
if password and password != password_confirm:
|
||||
raise forms.ValidationError("비밀번호가 일치하지 않습니다.")
|
||||
return cleaned_data
|
||||
|
||||
def save(self, commit=True):
|
||||
user = super().save(commit=False)
|
||||
password = self.cleaned_data.get("password")
|
||||
if password:
|
||||
user.set_password(password)
|
||||
if commit:
|
||||
user.save()
|
||||
return user
|
||||
|
||||
class SSHKeyForm(forms.ModelForm):
|
||||
"""SSH Private Key 관리 폼"""
|
||||
private_key = forms.CharField(
|
||||
widget=forms.Textarea(attrs={
|
||||
'class': 'form-control',
|
||||
'rows': 5,
|
||||
'placeholder': 'Enter your SSH Private Key',
|
||||
}),
|
||||
required=True,
|
||||
label="SSH Private Key"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = CustomUser
|
||||
fields = [] # 모델 필드는 직접 관리하므로 비움
|
||||
|
||||
def save(self, commit=True):
|
||||
"""SSH Private Key 암호화 후 저장"""
|
||||
user = self.instance
|
||||
private_key = self.cleaned_data['private_key']
|
||||
user.save_private_key(private_key)
|
||||
return user
|
45
custom_auth/migrations/0001_initial.py
Normal file
45
custom_auth/migrations/0001_initial.py
Normal file
@ -0,0 +1,45 @@
|
||||
# Generated by Django 4.2.14 on 2024-11-05 02:06
|
||||
|
||||
import django.contrib.auth.models
|
||||
import django.contrib.auth.validators
|
||||
from django.db import migrations, models
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('auth', '0012_alter_user_first_name_max_length'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CustomUser',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
||||
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
||||
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
|
||||
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
|
||||
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
|
||||
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
|
||||
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
||||
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
||||
('grade', models.CharField(blank=True, max_length=50, null=True)),
|
||||
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
|
||||
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'user',
|
||||
'verbose_name_plural': 'users',
|
||||
'abstract': False,
|
||||
},
|
||||
managers=[
|
||||
('objects', django.contrib.auth.models.UserManager()),
|
||||
],
|
||||
),
|
||||
]
|
@ -0,0 +1,23 @@
|
||||
# Generated by Django 4.2.14 on 2024-11-12 03:35
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('custom_auth', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='customuser',
|
||||
name='tenant_id_kr1',
|
||||
field=models.CharField(blank=True, max_length=100, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='customuser',
|
||||
name='tenant_id_kr2',
|
||||
field=models.CharField(blank=True, max_length=100, null=True),
|
||||
),
|
||||
]
|
18
custom_auth/migrations/0003_customuser_nhnc_id.py
Normal file
18
custom_auth/migrations/0003_customuser_nhnc_id.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.2.14 on 2024-11-12 13:13
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('custom_auth', '0002_customuser_tenant_id_kr1_customuser_tenant_id_kr2'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='customuser',
|
||||
name='nhnc_id',
|
||||
field=models.CharField(blank=True, max_length=100, null=True),
|
||||
),
|
||||
]
|
@ -0,0 +1,22 @@
|
||||
# Generated by Django 4.2.14 on 2024-11-12 13:18
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('custom_auth', '0003_customuser_nhnc_id'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='customuser',
|
||||
old_name='tenant_id_kr1',
|
||||
new_name='nhnc_api_tenant_id',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='customuser',
|
||||
name='tenant_id_kr2',
|
||||
),
|
||||
]
|
@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.2.14 on 2024-12-12 13:36
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('custom_auth', '0004_rename_tenant_id_kr1_customuser_nhnc_api_tenant_id_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='customuser',
|
||||
name='encrypted_private_key',
|
||||
field=models.BinaryField(blank=True, null=True),
|
||||
),
|
||||
]
|
0
custom_auth/migrations/__init__.py
Normal file
0
custom_auth/migrations/__init__.py
Normal file
42
custom_auth/models.py
Normal file
42
custom_auth/models.py
Normal file
@ -0,0 +1,42 @@
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.db import models
|
||||
import base64
|
||||
from cryptography.fernet import Fernet
|
||||
|
||||
|
||||
class CustomUser(AbstractUser):
|
||||
"""사용자 모델 - 기존 필드 + SSH Private Key 관리 필드"""
|
||||
|
||||
# 기존 필드 유지
|
||||
grade = models.CharField(max_length=50, blank=True, null=True)
|
||||
nhnc_id = models.CharField(max_length=100, blank=True, null=True)
|
||||
nhnc_api_tenant_id = models.CharField(max_length=100, blank=True, null=True)
|
||||
|
||||
"""사용자 모델 - SSH Private Key 관리 필드"""
|
||||
encrypted_private_key = models.BinaryField(blank=True, null=True) # 암호화된 SSH 키
|
||||
|
||||
def encrypt_private_key(self, private_key: str) -> bytes:
|
||||
"""SSH Private Key 암호화"""
|
||||
cipher = Fernet(self.get_encryption_key())
|
||||
return cipher.encrypt(private_key.encode())
|
||||
|
||||
def decrypt_private_key(self) -> str:
|
||||
"""SSH Private Key 복호화"""
|
||||
if self.encrypted_private_key:
|
||||
cipher = Fernet(self.get_encryption_key())
|
||||
return cipher.decrypt(self.encrypted_private_key).decode()
|
||||
return ""
|
||||
|
||||
def save_private_key(self, private_key: str):
|
||||
"""SSH Private Key 저장 (암호화 후)"""
|
||||
self.encrypted_private_key = self.encrypt_private_key(private_key)
|
||||
self.save()
|
||||
|
||||
def get_encryption_key(self) -> bytes:
|
||||
"""Fernet 키 생성 (username 기반)"""
|
||||
username_encoded = self.username.encode() # 사용자 이름을 바이트로 인코딩
|
||||
base64_key = base64.urlsafe_b64encode(username_encoded.ljust(32)[:32])
|
||||
return base64_key
|
||||
|
||||
def __str__(self):
|
||||
return self.username
|
27
custom_auth/templates/custom_auth/login.html
Normal file
27
custom_auth/templates/custom_auth/login.html
Normal file
@ -0,0 +1,27 @@
|
||||
{% extends "components/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Login</h2>
|
||||
|
||||
<!-- 로그인 실패 메시지 표시 -->
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Username</label>
|
||||
<input type="text" class="form-control" id="username" name="username" required="required">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required="required">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100">Login</button>
|
||||
</form>
|
||||
{% endblock %}
|
10
custom_auth/templates/custom_auth/login_django_default.html
Normal file
10
custom_auth/templates/custom_auth/login_django_default.html
Normal file
@ -0,0 +1,10 @@
|
||||
{% extends "components/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Login</h2>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
{% endblock %}
|
6
custom_auth/templates/custom_auth/profile.html
Normal file
6
custom_auth/templates/custom_auth/profile.html
Normal file
@ -0,0 +1,6 @@
|
||||
{% extends "components/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Welcome, {{ user.username }}!</h2>
|
||||
<p>This is your profile page.</p>
|
||||
{% endblock %}
|
10
custom_auth/templates/custom_auth/signup.html
Normal file
10
custom_auth/templates/custom_auth/signup.html
Normal file
@ -0,0 +1,10 @@
|
||||
{% extends "components/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Sign Up</h2>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button type="submit" class="btn btn-primary">Sign Up</button>
|
||||
</form>
|
||||
{% endblock %}
|
23
custom_auth/templates/custom_auth/upload_ssh_key.html
Normal file
23
custom_auth/templates/custom_auth/upload_ssh_key.html
Normal file
@ -0,0 +1,23 @@
|
||||
{% extends "components/base.html" %}
|
||||
|
||||
{% block main_area %}
|
||||
<h1>Upload SSH Private Key</h1>
|
||||
|
||||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button type="submit" class="btn btn-primary">Upload</button>
|
||||
</form>
|
||||
|
||||
<!-- 복호화된 SSH Key 표시 -->
|
||||
{% if decrypted_key %}
|
||||
<hr>
|
||||
<p class="text-muted mt-4">Upload 클릭 후 이전 SSH Key가 업데이트 됩니다.</p>
|
||||
<h3>Stored SSH Private Key (Decrypted)</h3>
|
||||
<pre class="bg-light p-3">{{ decrypted_key }}</pre>
|
||||
{% else %}
|
||||
<!-- <p class="text-muted mt-4">No SSH Private Key stored.</p> -->
|
||||
<p class="text-muted mt-4">Upload 클릭 후 이전 SSH Key가 업데이트 됩니다.</p>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
3
custom_auth/tests.py
Normal file
3
custom_auth/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
14
custom_auth/urls.py
Normal file
14
custom_auth/urls.py
Normal file
@ -0,0 +1,14 @@
|
||||
from django.urls import path
|
||||
from django.contrib.auth import views as auth_views
|
||||
from . import views
|
||||
|
||||
app_name = 'custom_auth'
|
||||
|
||||
urlpatterns = [
|
||||
# path('login/', auth_views.LoginView.as_view(template_name='custom_auth/login.html'), name='login'),
|
||||
path('login/', views.login_view, name='login'),
|
||||
path('logout/', auth_views.LogoutView.as_view(), name='logout'),
|
||||
path('signup/', views.signup_view, name='signup'),
|
||||
path('edit-profile/', views.edit_profile_view, name='edit_profile'),
|
||||
path('upload_ssh_key/', views.upload_ssh_key, name='upload_ssh_key'),
|
||||
]
|
94
custom_auth/views.py
Normal file
94
custom_auth/views.py
Normal file
@ -0,0 +1,94 @@
|
||||
from django.shortcuts import render, redirect
|
||||
from .forms import CustomUserCreationForm, CustomUserChangeForm
|
||||
from django.contrib.auth import login, authenticate
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth import update_session_auth_hash
|
||||
from django.shortcuts import render, redirect
|
||||
from .forms import SSHKeyForm
|
||||
|
||||
@login_required
|
||||
def edit_profile_view(request):
|
||||
if request.method == "POST":
|
||||
form = CustomUserChangeForm(request.POST, instance=request.user)
|
||||
if form.is_valid():
|
||||
user = form.save()
|
||||
# 세션 인증 유지
|
||||
update_session_auth_hash(request, user)
|
||||
messages.success(request, "프로필이 성공적으로 업데이트되었습니다.")
|
||||
return redirect("/") # 리다이렉트할 URL
|
||||
else:
|
||||
form = CustomUserChangeForm(instance=request.user)
|
||||
|
||||
return render(request, "custom_auth/edit_profile.html", {"form": form})
|
||||
|
||||
|
||||
def signup_view(request):
|
||||
if request.method == "POST":
|
||||
form = CustomUserCreationForm(request.POST)
|
||||
if form.is_valid():
|
||||
user = form.save()
|
||||
# 로그인 처리 (선택적)
|
||||
# login(request, user)
|
||||
return redirect("/")
|
||||
# return redirect('custom_auth:login')
|
||||
else:
|
||||
form = CustomUserCreationForm()
|
||||
return render(request, "custom_auth/signup.html", {"form": form})
|
||||
|
||||
|
||||
def login_view(request):
|
||||
if request.method == "POST":
|
||||
username = request.POST.get("username")
|
||||
password = request.POST.get("password")
|
||||
user = authenticate(request, username=username, password=password)
|
||||
if user is not None:
|
||||
login(request, user)
|
||||
return redirect("/") # 원하는 URL로 리다이렉트 (여기서는 'home'으로 설정)
|
||||
else:
|
||||
messages.error(request, "Invalid username or password.")
|
||||
return redirect("/") # 로그인 실패 시 루트 URL로 리다이렉트
|
||||
|
||||
return render(request, "custom_auth/login.html")
|
||||
|
||||
@login_required
|
||||
# def upload_ssh_key(request):
|
||||
# """SSH Private Key 업로드 뷰"""
|
||||
# user = request.user
|
||||
# if request.method == 'POST':
|
||||
# form = SSHKeyForm(request.POST, instance=user)
|
||||
# if form.is_valid():
|
||||
# form.save()
|
||||
# return redirect('custom_auth:upload_ssh_key') # 성공 시 동일 페이지 리다이렉트
|
||||
# else:
|
||||
# form = SSHKeyForm(instance=user)
|
||||
|
||||
# return render(request, 'custom_auth/upload_ssh_key.html', {'form': form})
|
||||
|
||||
# @login_required
|
||||
def upload_ssh_key(request):
|
||||
"""SSH Private Key 업로드 및 복호화 표시 뷰"""
|
||||
user = request.user
|
||||
decrypted_key = "" # 기본 값
|
||||
|
||||
# POST 요청 처리
|
||||
if request.method == 'POST':
|
||||
form = SSHKeyForm(request.POST, instance=user)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
return redirect('custom_auth:upload_ssh_key') # 성공 시 리다이렉트
|
||||
else:
|
||||
form = SSHKeyForm(instance=user)
|
||||
|
||||
# 복호화된 SSH Key 가져오기
|
||||
if user.encrypted_private_key:
|
||||
try:
|
||||
decrypted_key = user.decrypt_private_key()
|
||||
except Exception as e:
|
||||
decrypted_key = f"복호화 오류: {str(e)}"
|
||||
|
||||
# 템플릿으로 데이터 전달
|
||||
return render(request, 'custom_auth/upload_ssh_key.html', {
|
||||
'form': form,
|
||||
'decrypted_key': decrypted_key
|
||||
})
|
Reference in New Issue
Block a user