mirror of https://github.com/jumpserver/jumpserver
[Update] merge
commit
a5fc04e0ce
|
@ -0,0 +1,158 @@
|
|||
# Generated by Django 2.1.7 on 2019-02-28 10:16
|
||||
|
||||
import assets.models.asset
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('assets', '0002_auto_20180105_1807'), ('assets', '0003_auto_20180109_2331'), ('assets', '0004_auto_20180125_1218'), ('assets', '0005_auto_20180126_1637'), ('assets', '0006_auto_20180130_1502'), ('assets', '0007_auto_20180225_1815'), ('assets', '0008_auto_20180306_1804'), ('assets', '0009_auto_20180307_1212')]
|
||||
|
||||
dependencies = [
|
||||
('assets', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='adminuser',
|
||||
options={'ordering': ['name'], 'verbose_name': 'Admin user'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='asset',
|
||||
options={'verbose_name': 'Asset'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='assetgroup',
|
||||
options={'ordering': ['name'], 'verbose_name': 'Asset group'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='cluster',
|
||||
options={'ordering': ['name'], 'verbose_name': 'Cluster'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='systemuser',
|
||||
options={'ordering': ['name'], 'verbose_name': 'System user'},
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='cluster',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='assetgroup',
|
||||
name='created_by',
|
||||
field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Label',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||
('value', models.CharField(max_length=128, verbose_name='Value')),
|
||||
('category', models.CharField(choices=[('S', 'System'), ('U', 'User')], default='U', max_length=128, verbose_name='Category')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
|
||||
('comment', models.TextField(blank=True, null=True, verbose_name='Comment')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'assets_label',
|
||||
},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='label',
|
||||
unique_together={('name', 'value')},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='labels',
|
||||
field=models.ManyToManyField(blank=True, related_name='assets', to='assets.Label', verbose_name='Labels'),
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='cabinet_no',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='cabinet_pos',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='env',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='remote_card_ip',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='status',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='type',
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Node',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('key', models.CharField(max_length=64, unique=True, verbose_name='Key')),
|
||||
('value', models.CharField(max_length=128, verbose_name='Value')),
|
||||
('child_mark', models.IntegerField(default=0)),
|
||||
('date_create', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='groups',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='systemuser',
|
||||
name='cluster',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='admin_user',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='assets.AdminUser', verbose_name='Admin user'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='protocol',
|
||||
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp')], default='ssh', max_length=16, verbose_name='Protocol'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='nodes',
|
||||
field=models.ManyToManyField(default=assets.models.asset.default_node, related_name='assets', to='assets.Node', verbose_name='Nodes'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='nodes',
|
||||
field=models.ManyToManyField(blank=True, to='assets.Node', verbose_name='Nodes'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='created_by',
|
||||
field=models.CharField(max_length=128, null=True, verbose_name='Created by'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=128, verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='platform',
|
||||
field=models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Other', 'Other')], default='Linux', max_length=128, verbose_name='Platform'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='created_by',
|
||||
field=models.CharField(max_length=128, null=True, verbose_name='Created by'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=128, verbose_name='Username'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,220 @@
|
|||
# Generated by Django 2.1.7 on 2019-02-28 10:16
|
||||
|
||||
import assets.models.utils
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
# Functions from the following migrations need manual copying.
|
||||
# Move them and any dependencies into this file, then update the
|
||||
# RunPython operations to refer to the local versions:
|
||||
# assets.migrations.0017_auto_20180702_1415
|
||||
|
||||
def migrate_win_to_ssh_protocol(apps, schema_editor):
|
||||
asset_model = apps.get_model("assets", "Asset")
|
||||
db_alias = schema_editor.connection.alias
|
||||
asset_model.objects.using(db_alias).filter(platform__startswith='Win').update(protocol='rdp')
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('assets', '0010_auto_20180307_1749'), ('assets', '0011_auto_20180326_0957'), ('assets', '0012_auto_20180404_1302'), ('assets', '0013_auto_20180411_1135'), ('assets', '0014_auto_20180427_1245'), ('assets', '0015_auto_20180510_1235'), ('assets', '0016_auto_20180511_1203'), ('assets', '0017_auto_20180702_1415'), ('assets', '0018_auto_20180807_1116'), ('assets', '0019_auto_20180816_1320')]
|
||||
|
||||
dependencies = [
|
||||
('assets', '0009_auto_20180307_1212'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='node',
|
||||
name='value',
|
||||
field=models.CharField(max_length=128, unique=True, verbose_name='Value'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Domain',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=128, unique=True, verbose_name='Name')),
|
||||
('comment', models.TextField(blank=True, verbose_name='Comment')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Gateway',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=128, unique=True, verbose_name='Name')),
|
||||
('username', models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username')),
|
||||
('_password', models.CharField(blank=True, max_length=256, null=True, verbose_name='Password')),
|
||||
('_private_key', models.TextField(blank=True, max_length=4096, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key')),
|
||||
('_public_key', models.TextField(blank=True, max_length=4096, verbose_name='SSH public key')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True)),
|
||||
('date_updated', models.DateTimeField(auto_now=True)),
|
||||
('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')),
|
||||
('ip', models.GenericIPAddressField(db_index=True, verbose_name='IP')),
|
||||
('port', models.IntegerField(default=22, verbose_name='Port')),
|
||||
('protocol', models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp')], default='ssh', max_length=16, verbose_name='Protocol')),
|
||||
('comment', models.CharField(blank=True, max_length=128, null=True, verbose_name='Comment')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
|
||||
('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Domain', verbose_name='Domain')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='domain',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assets', to='assets.Domain', verbose_name='Domain'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='assets',
|
||||
field=models.ManyToManyField(blank=True, to='assets.Asset', verbose_name='Assets'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='sudo',
|
||||
field=models.TextField(default='/bin/whoami', verbose_name='Sudo'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='node',
|
||||
name='value',
|
||||
field=models.CharField(max_length=128, verbose_name='Value'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='protocol',
|
||||
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)')], default='ssh', max_length=128, verbose_name='Protocol'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='login_mode',
|
||||
field=models.CharField(choices=[('auto', 'Automatic login'), ('manual', 'Manually login')], default='auto', max_length=10, verbose_name='Login mode'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='username',
|
||||
field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='platform',
|
||||
field=models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Windows2016', 'Windows(2016)'), ('Other', 'Other')], default='Linux', max_length=128, verbose_name='Platform'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='protocol',
|
||||
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)')], default='ssh', max_length=16, verbose_name='Protocol'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='username',
|
||||
field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=migrate_win_to_ssh_protocol,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='adminuser',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='domain',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='gateway',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='label',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='node',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='name',
|
||||
field=models.CharField(max_length=128, verbose_name='Name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='hostname',
|
||||
field=models.CharField(max_length=128, verbose_name='Hostname'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='name',
|
||||
field=models.CharField(max_length=128, verbose_name='Name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='name',
|
||||
field=models.CharField(max_length=128, verbose_name='Name'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='adminuser',
|
||||
unique_together={('name', 'org_id')},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='cpu_vcpus',
|
||||
field=models.IntegerField(null=True, verbose_name='CPU vcpus'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='asset',
|
||||
unique_together={('org_id', 'hostname')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='gateway',
|
||||
unique_together={('name', 'org_id')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='systemuser',
|
||||
unique_together={('name', 'org_id')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='label',
|
||||
unique_together={('name', 'value', 'org_id')},
|
||||
),
|
||||
]
|
|
@ -1,4 +1,2 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from users.models import LoginLog
|
|
@ -38,14 +38,4 @@ class Migration(migrations.Migration):
|
|||
('datetime', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UserLoginLog',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
'proxy': True,
|
||||
'indexes': [],
|
||||
},
|
||||
bases=('users.loginlog',),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
# Generated by Django 2.1.7 on 2019-02-28 09:15
|
||||
|
||||
from django.db import migrations, models, connection
|
||||
import django.utils.timezone
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('audits', '0004_operatelog_passwordchangelog_userloginlog'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='UserLoginLog',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('username', models.CharField(max_length=128, verbose_name='Username')),
|
||||
('type', models.CharField(choices=[('W', 'Web'), ('T', 'Terminal')], max_length=2, verbose_name='Login type')),
|
||||
('ip', models.GenericIPAddressField(verbose_name='Login ip')),
|
||||
('city', models.CharField(blank=True, max_length=254, null=True, verbose_name='Login city')),
|
||||
('user_agent', models.CharField(blank=True, max_length=254, null=True, verbose_name='User agent')),
|
||||
('mfa', models.SmallIntegerField(choices=[(0, 'Disabled'), (1, 'Enabled'), (2, '-')], default=2, verbose_name='MFA')),
|
||||
('reason', models.SmallIntegerField(choices=[(0, '-'), (1, 'Username/password check failed'), (2, 'MFA authentication failed'), (3, 'Username does not exist'), (4, 'Password expired')], default=0, verbose_name='Reason')),
|
||||
('status', models.BooleanField(choices=[(True, 'Success'), (False, 'Failed')], default=True, max_length=2, verbose_name='Status')),
|
||||
('datetime', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Date login')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-datetime', 'username'],
|
||||
},
|
||||
),
|
||||
]
|
||||
drop_table_sql = "DROP TABLE audits_userloginlog"
|
||||
rename_table_sql = "RENAME TABLE users_loginlog TO audits_userloginlog"
|
||||
table_exist = 'users_loginlog' in connection.introspection.table_names()
|
||||
if table_exist:
|
||||
operations.append(migrations.RunSQL(drop_table_sql))
|
||||
operations.append(migrations.RunSQL(rename_table_sql))
|
|
@ -2,9 +2,9 @@ import uuid
|
|||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils import timezone
|
||||
|
||||
from orgs.mixins import OrgModelMixin
|
||||
from .hands import LoginLog
|
||||
|
||||
__all__ = [
|
||||
'FTPLog', 'OperateLog', 'PasswordChangeLog', 'UserLoginLog',
|
||||
|
@ -55,6 +55,50 @@ class PasswordChangeLog(models.Model):
|
|||
return "{} change {}'s password".format(self.change_by, self.user)
|
||||
|
||||
|
||||
class UserLoginLog(LoginLog):
|
||||
class UserLoginLog(models.Model):
|
||||
LOGIN_TYPE_CHOICE = (
|
||||
('W', 'Web'),
|
||||
('T', 'Terminal'),
|
||||
)
|
||||
|
||||
MFA_DISABLED = 0
|
||||
MFA_ENABLED = 1
|
||||
MFA_UNKNOWN = 2
|
||||
|
||||
MFA_CHOICE = (
|
||||
(MFA_DISABLED, _('Disabled')),
|
||||
(MFA_ENABLED, _('Enabled')),
|
||||
(MFA_UNKNOWN, _('-')),
|
||||
)
|
||||
|
||||
REASON_NOTHING = 0
|
||||
REASON_PASSWORD = 1
|
||||
REASON_MFA = 2
|
||||
REASON_NOT_EXIST = 3
|
||||
REASON_PASSWORD_EXPIRED = 4
|
||||
|
||||
REASON_CHOICE = (
|
||||
(REASON_NOTHING, _('-')),
|
||||
(REASON_PASSWORD, _('Username/password check failed')),
|
||||
(REASON_MFA, _('MFA authentication failed')),
|
||||
(REASON_NOT_EXIST, _("Username does not exist")),
|
||||
(REASON_PASSWORD_EXPIRED, _("Password expired")),
|
||||
)
|
||||
|
||||
STATUS_CHOICE = (
|
||||
(True, _('Success')),
|
||||
(False, _('Failed'))
|
||||
)
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
username = models.CharField(max_length=128, verbose_name=_('Username'))
|
||||
type = models.CharField(choices=LOGIN_TYPE_CHOICE, max_length=2, verbose_name=_('Login type'))
|
||||
ip = models.GenericIPAddressField(verbose_name=_('Login ip'))
|
||||
city = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('Login city'))
|
||||
user_agent = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('User agent'))
|
||||
mfa = models.SmallIntegerField(default=MFA_UNKNOWN, choices=MFA_CHOICE, verbose_name=_('MFA'))
|
||||
reason = models.SmallIntegerField(default=0, choices=REASON_CHOICE, verbose_name=_('Reason'))
|
||||
status = models.BooleanField(max_length=2, default=True, choices=STATUS_CHOICE, verbose_name=_('Status'))
|
||||
datetime = models.DateTimeField(default=timezone.now, verbose_name=_('Date login'))
|
||||
|
||||
class Meta:
|
||||
proxy = True
|
||||
ordering = ['-datetime', 'username']
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import datetime
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
from celery import shared_task
|
||||
|
||||
from ops.celery.decorator import register_as_period_task
|
||||
from .models import UserLoginLog
|
||||
|
||||
|
||||
@register_as_period_task(interval=3600*24)
|
||||
@shared_task
|
||||
def clean_login_log_period():
|
||||
now = timezone.now()
|
||||
try:
|
||||
days = int(settings.LOGIN_LOG_KEEP_DAYS)
|
||||
except ValueError:
|
||||
days = 90
|
||||
expired_day = now - datetime.timedelta(days=days)
|
||||
UserLoginLog.objects.filter(datetime__lt=expired_day).delete()
|
|
@ -8,7 +8,6 @@ from common.permissions import AdminUserRequiredMixin
|
|||
|
||||
from orgs.utils import current_org
|
||||
from ops.views import CommandExecutionListView as UserCommandExecutionListView
|
||||
from users.models import User
|
||||
from .models import FTPLog, OperateLog, PasswordChangeLog, UserLoginLog
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from .auth import *
|
|
@ -1,5 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import uuid
|
||||
|
||||
from django.core.cache import cache
|
||||
|
@ -14,16 +15,21 @@ from rest_framework.views import APIView
|
|||
from common.utils import get_logger, get_request_ip
|
||||
from common.permissions import IsOrgAdminOrAppUser
|
||||
from orgs.mixins import RootOrgViewMixin
|
||||
from ..serializers import UserSerializer
|
||||
from ..tasks import write_login_log_async
|
||||
from ..models import User, LoginLog
|
||||
from ..utils import check_user_valid, check_otp_code, \
|
||||
increase_login_failed_count, is_block_login, \
|
||||
clean_failed_count
|
||||
from ..hands import Asset, SystemUser
|
||||
from users.serializers import UserSerializer
|
||||
from users.models import User
|
||||
from assets.models import Asset, SystemUser
|
||||
from audits.models import UserLoginLog as LoginLog
|
||||
from users.utils import (
|
||||
check_user_valid, check_otp_code, increase_login_failed_count,
|
||||
is_block_login, clean_failed_count
|
||||
)
|
||||
|
||||
from ..signals import post_auth_success, post_auth_failed
|
||||
|
||||
logger = get_logger(__name__)
|
||||
__all__ = [
|
||||
'UserAuthApi', 'UserConnectionTokenApi', 'UserOtpAuthApi',
|
||||
]
|
||||
|
||||
|
||||
class UserAuthApi(RootOrgViewMixin, APIView):
|
||||
|
@ -46,37 +52,22 @@ class UserAuthApi(RootOrgViewMixin, APIView):
|
|||
username = request.data.get('username', '')
|
||||
exist = User.objects.filter(username=username).first()
|
||||
reason = LoginLog.REASON_PASSWORD if exist else LoginLog.REASON_NOT_EXIST
|
||||
data = {
|
||||
'username': username,
|
||||
'mfa': LoginLog.MFA_UNKNOWN,
|
||||
'reason': reason,
|
||||
'status': False
|
||||
}
|
||||
self.write_login_log(request, data)
|
||||
self.send_auth_signal(success=False, username=username, reason=reason)
|
||||
increase_login_failed_count(username, ip)
|
||||
return Response({'msg': msg}, status=401)
|
||||
|
||||
if user.password_has_expired:
|
||||
data = {
|
||||
'username': user.username,
|
||||
'mfa': int(user.otp_enabled),
|
||||
'reason': LoginLog.REASON_PASSWORD_EXPIRED,
|
||||
'status': False
|
||||
}
|
||||
self.write_login_log(request, data)
|
||||
self.send_auth_signal(
|
||||
success=False, username=username,
|
||||
reason=LoginLog.REASON_PASSWORD_EXPIRED
|
||||
)
|
||||
msg = _("The user {} password has expired, please update.".format(
|
||||
user.username))
|
||||
logger.info(msg)
|
||||
return Response({'msg': msg}, status=401)
|
||||
|
||||
if not user.otp_enabled:
|
||||
data = {
|
||||
'username': user.username,
|
||||
'mfa': int(user.otp_enabled),
|
||||
'reason': LoginLog.REASON_NOTHING,
|
||||
'status': True
|
||||
}
|
||||
self.write_login_log(request, data)
|
||||
self.send_auth_signal(success=True, user=user)
|
||||
# 登陆成功,清除原来的缓存计数
|
||||
clean_failed_count(username, ip)
|
||||
token = user.create_bearer_token(request)
|
||||
|
@ -91,7 +82,7 @@ class UserAuthApi(RootOrgViewMixin, APIView):
|
|||
'code': 101,
|
||||
'msg': _('Please carry seed value and '
|
||||
'conduct MFA secondary certification'),
|
||||
'otp_url': reverse('api-users:user-otp-auth'),
|
||||
'otp_url': reverse('api-auth:user-otp-auth'),
|
||||
'seed': seed,
|
||||
'user': self.serializer_class(user).data
|
||||
}, status=300
|
||||
|
@ -108,22 +99,14 @@ class UserAuthApi(RootOrgViewMixin, APIView):
|
|||
)
|
||||
return user, msg
|
||||
|
||||
@staticmethod
|
||||
def write_login_log(request, data):
|
||||
login_ip = request.data.get('remote_addr', None)
|
||||
login_type = request.data.get('login_type', '')
|
||||
user_agent = request.data.get('HTTP_USER_AGENT', '')
|
||||
|
||||
if not login_ip:
|
||||
login_ip = get_request_ip(request)
|
||||
|
||||
tmp_data = {
|
||||
'ip': login_ip,
|
||||
'type': login_type,
|
||||
'user_agent': user_agent,
|
||||
}
|
||||
data.update(tmp_data)
|
||||
write_login_log_async.delay(**data)
|
||||
def send_auth_signal(self, success=True, user=None, username='', reason=''):
|
||||
if success:
|
||||
post_auth_success.send(sender=self.__class__, user=user, request=self.request)
|
||||
else:
|
||||
post_auth_failed.send(
|
||||
sender=self.__class__, username=username,
|
||||
request=self.request, reason=reason
|
||||
)
|
||||
|
||||
|
||||
class UserConnectionTokenApi(RootOrgViewMixin, APIView):
|
||||
|
@ -167,29 +150,6 @@ class UserConnectionTokenApi(RootOrgViewMixin, APIView):
|
|||
return super().get_permissions()
|
||||
|
||||
|
||||
class UserToken(APIView):
|
||||
permission_classes = (AllowAny,)
|
||||
|
||||
def post(self, request):
|
||||
if not request.user.is_authenticated:
|
||||
username = request.data.get('username', '')
|
||||
email = request.data.get('email', '')
|
||||
password = request.data.get('password', '')
|
||||
public_key = request.data.get('public_key', '')
|
||||
|
||||
user, msg = check_user_valid(
|
||||
username=username, email=email,
|
||||
password=password, public_key=public_key)
|
||||
else:
|
||||
user = request.user
|
||||
msg = None
|
||||
if user:
|
||||
token = user.create_bearer_token(request)
|
||||
return Response({'Token': token, 'Keyword': 'Bearer'}, status=200)
|
||||
else:
|
||||
return Response({'error': msg}, status=406)
|
||||
|
||||
|
||||
class UserOtpAuthApi(RootOrgViewMixin, APIView):
|
||||
permission_classes = (AllowAny,)
|
||||
serializer_class = UserSerializer
|
||||
|
@ -197,52 +157,25 @@ class UserOtpAuthApi(RootOrgViewMixin, APIView):
|
|||
def post(self, request):
|
||||
otp_code = request.data.get('otp_code', '')
|
||||
seed = request.data.get('seed', '')
|
||||
|
||||
user = cache.get(seed, None)
|
||||
if not user:
|
||||
return Response(
|
||||
{'msg': _('Please verify the user name and password first')},
|
||||
status=401
|
||||
)
|
||||
|
||||
if not check_otp_code(user.otp_secret_key, otp_code):
|
||||
data = {
|
||||
'username': user.username,
|
||||
'mfa': int(user.otp_enabled),
|
||||
'reason': LoginLog.REASON_MFA,
|
||||
'status': False
|
||||
}
|
||||
self.write_login_log(request, data)
|
||||
self.send_auth_signal(success=False, username=user.username, reason=LoginLog.REASON_MFA)
|
||||
return Response({'msg': _('MFA certification failed')}, status=401)
|
||||
|
||||
data = {
|
||||
'username': user.username,
|
||||
'mfa': int(user.otp_enabled),
|
||||
'reason': LoginLog.REASON_NOTHING,
|
||||
'status': True
|
||||
}
|
||||
self.write_login_log(request, data)
|
||||
self.send_auth_signal(success=True, user=user)
|
||||
token = user.create_bearer_token(request)
|
||||
return Response(
|
||||
{
|
||||
'token': token,
|
||||
'user': self.serializer_class(user).data
|
||||
}
|
||||
)
|
||||
data = {'token': token, 'user': self.serializer_class(user).data}
|
||||
return Response(data)
|
||||
|
||||
@staticmethod
|
||||
def write_login_log(request, data):
|
||||
login_ip = request.data.get('remote_addr', None)
|
||||
login_type = request.data.get('login_type', '')
|
||||
user_agent = request.data.get('HTTP_USER_AGENT', '')
|
||||
|
||||
if not login_ip:
|
||||
login_ip = get_request_ip(request)
|
||||
|
||||
tmp_data = {
|
||||
'ip': login_ip,
|
||||
'type': login_type,
|
||||
'user_agent': user_agent
|
||||
}
|
||||
data.update(tmp_data)
|
||||
write_login_log_async.delay(**data)
|
||||
def send_auth_signal(self, success=True, user=None, username='', reason=''):
|
||||
if success:
|
||||
post_auth_success.send(sender=self.__class__, user=user, request=self.request)
|
||||
else:
|
||||
post_auth_failed.send(
|
||||
sender=self.__class__, username=username,
|
||||
request=self.request, reason=reason
|
||||
)
|
|
@ -8,13 +8,13 @@ from django.core.cache import cache
|
|||
from django.conf import settings
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.six import text_type
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib.auth import get_user_model
|
||||
from rest_framework import HTTP_HEADER_ENCODING
|
||||
from rest_framework import authentication, exceptions
|
||||
from rest_framework.authentication import CSRFCheck
|
||||
|
||||
from common.utils import get_object_or_none, make_signature, http_to_unixtime
|
||||
from .models import User, AccessKey, PrivateToken
|
||||
from ..models import AccessKey, PrivateToken
|
||||
|
||||
|
||||
def get_request_date_header(request):
|
||||
|
@ -42,7 +42,6 @@ class AccessKeyAuthentication(authentication.BaseAuthentication):
|
|||
失败
|
||||
"""
|
||||
keyword = 'Sign'
|
||||
model = AccessKey
|
||||
|
||||
def authenticate(self, request):
|
||||
auth = authentication.get_authorization_header(request).split()
|
||||
|
@ -109,7 +108,7 @@ class AccessKeyAuthentication(authentication.BaseAuthentication):
|
|||
|
||||
class AccessTokenAuthentication(authentication.BaseAuthentication):
|
||||
keyword = 'Bearer'
|
||||
model = User
|
||||
model = get_user_model()
|
||||
expiration = settings.TOKEN_EXPIRATION or 3600
|
||||
|
||||
def authenticate(self, request):
|
||||
|
@ -133,10 +132,9 @@ class AccessTokenAuthentication(authentication.BaseAuthentication):
|
|||
raise exceptions.AuthenticationFailed(msg)
|
||||
return self.authenticate_credentials(token)
|
||||
|
||||
@staticmethod
|
||||
def authenticate_credentials(token):
|
||||
def authenticate_credentials(self, token):
|
||||
user_id = cache.get(token)
|
||||
user = get_object_or_none(User, id=user_id)
|
||||
user = get_object_or_none(self.model, id=user_id)
|
||||
|
||||
if not user:
|
||||
msg = _('Invalid token or cache refreshed.')
|
|
@ -16,6 +16,8 @@ class LDAPAuthorizationBackend(LDAPBackend):
|
|||
"""
|
||||
|
||||
def authenticate(self, request=None, username=None, password=None, **kwargs):
|
||||
if not username:
|
||||
return None
|
||||
ldap_user = LDAPUser(self, username=username.strip(), request=request)
|
||||
user = self.authenticate_ldap_user(ldap_user, password)
|
||||
return user
|
|
@ -0,0 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from .backends import *
|
||||
from .middleware import *
|
||||
from .utils import *
|
|
@ -4,16 +4,19 @@
|
|||
from django.contrib.auth import get_user_model
|
||||
from django.conf import settings
|
||||
|
||||
from . import client
|
||||
from common.utils import get_logger
|
||||
from authentication.openid.models import OIDT_ACCESS_TOKEN
|
||||
from .utils import new_client
|
||||
from .models import OIDT_ACCESS_TOKEN
|
||||
|
||||
UserModel = get_user_model()
|
||||
|
||||
logger = get_logger(__file__)
|
||||
client = new_client()
|
||||
|
||||
BACKEND_OPENID_AUTH_CODE = \
|
||||
'authentication.openid.backends.OpenIDAuthorizationCodeBackend'
|
||||
|
||||
__all__ = [
|
||||
'OpenIDAuthorizationCodeBackend', 'OpenIDAuthorizationPasswordBackend',
|
||||
]
|
||||
|
||||
|
||||
class BaseOpenIDAuthorizationBackend(object):
|
||||
|
@ -72,7 +75,6 @@ class OpenIDAuthorizationPasswordBackend(BaseOpenIDAuthorizationBackend):
|
|||
|
||||
if not settings.AUTH_OPENID:
|
||||
return None
|
||||
|
||||
elif not username:
|
||||
return None
|
||||
|
|
@ -6,12 +6,13 @@ from django.contrib.auth import logout
|
|||
from django.utils.deprecation import MiddlewareMixin
|
||||
from django.contrib.auth import BACKEND_SESSION_KEY
|
||||
|
||||
from . import client
|
||||
from common.utils import get_logger
|
||||
from .backends import BACKEND_OPENID_AUTH_CODE
|
||||
from authentication.openid.models import OIDT_ACCESS_TOKEN
|
||||
from .utils import new_client
|
||||
from .models import OIDT_ACCESS_TOKEN
|
||||
|
||||
BACKEND_OPENID_AUTH_CODE = 'OpenIDAuthorizationCodeBackend'
|
||||
logger = get_logger(__file__)
|
||||
__all__ = ['OpenIDAuthenticationMiddleware']
|
||||
|
||||
|
||||
class OpenIDAuthenticationMiddleware(MiddlewareMixin):
|
||||
|
@ -20,22 +21,22 @@ class OpenIDAuthenticationMiddleware(MiddlewareMixin):
|
|||
"""
|
||||
|
||||
def process_request(self, request):
|
||||
|
||||
# Don't need openid auth if AUTH_OPENID is False
|
||||
if not settings.AUTH_OPENID:
|
||||
return
|
||||
|
||||
# Don't need check single logout if user not authenticated
|
||||
if not request.user.is_authenticated:
|
||||
return
|
||||
|
||||
elif request.session[BACKEND_SESSION_KEY] != BACKEND_OPENID_AUTH_CODE:
|
||||
elif request.session[BACKEND_SESSION_KEY].endswith(
|
||||
BACKEND_OPENID_AUTH_CODE):
|
||||
return
|
||||
|
||||
# Check openid user single logout or not with access_token
|
||||
client = new_client()
|
||||
try:
|
||||
client.openid_connect_client.userinfo(
|
||||
token=request.session.get(OIDT_ACCESS_TOKEN))
|
||||
token=request.session.get(OIDT_ACCESS_TOKEN)
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logout(request)
|
|
@ -5,7 +5,8 @@ from django.db import transaction
|
|||
from django.contrib.auth import get_user_model
|
||||
from keycloak.realm import KeycloakRealm
|
||||
from keycloak.keycloak_openid import KeycloakOpenID
|
||||
from ..signals import post_create_openid_user
|
||||
|
||||
from .signals import post_create_openid_user
|
||||
|
||||
OIDT_ACCESS_TOKEN = 'oidt_access_token'
|
||||
|
||||
|
@ -38,10 +39,6 @@ class Client(object):
|
|||
self.openid_connect_client = self.new_openid_connect_client()
|
||||
|
||||
def new_realm(self):
|
||||
"""
|
||||
:param authentication.openid.models.Realm realm:
|
||||
:return keycloak.realm.Realm:
|
||||
"""
|
||||
return KeycloakRealm(
|
||||
server_url=self.server_url,
|
||||
realm_name=self.realm_name,
|
||||
|
@ -76,7 +73,7 @@ class Client(object):
|
|||
|
||||
:param str username: authentication username
|
||||
:param str password: authentication password
|
||||
:return: authentication.models.OpenIDTokenProfile
|
||||
:return: OpenIDTokenProfile
|
||||
"""
|
||||
token_response = self.openid_client.token(
|
||||
username=username, password=password
|
||||
|
@ -93,7 +90,7 @@ class Client(object):
|
|||
|
||||
:param str code: authentication code
|
||||
:param str redirect_uri:
|
||||
:rtype: authentication.models.OpenIDTokenProfile
|
||||
:rtype: OpenIDTokenProfile
|
||||
"""
|
||||
|
||||
token_response = self.openid_connect_client.authorization_code(
|
||||
|
@ -114,7 +111,7 @@ class Client(object):
|
|||
- refresh_expires_in
|
||||
|
||||
:param dict token_response:
|
||||
:rtype: authentication.openid.models.OpenIDTokenProfile
|
||||
:rtype: OpenIDTokenProfile
|
||||
"""
|
||||
|
||||
userinfo = self.openid_connect_client.userinfo(
|
|
@ -0,0 +1,5 @@
|
|||
from django.dispatch import Signal
|
||||
|
||||
|
||||
post_create_openid_user = Signal(providing_args=('user',))
|
||||
post_openid_login_success = Signal(providing_args=('user', 'request'))
|
|
@ -0,0 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path('login/', views.OpenIDLoginView.as_view(), name='openid-login'),
|
||||
path('login/complete/', views.OpenIDLoginCompleteView.as_view(),
|
||||
name='openid-login-complete'),
|
||||
]
|
|
@ -4,6 +4,8 @@
|
|||
from django.conf import settings
|
||||
from .models import Client
|
||||
|
||||
__all__ = ['new_client']
|
||||
|
||||
|
||||
def new_client():
|
||||
"""
|
||||
|
@ -15,6 +17,3 @@ def new_client():
|
|||
client_id=settings.AUTH_OPENID_CLIENT_ID,
|
||||
client_secret=settings.AUTH_OPENID_CLIENT_SECRET
|
||||
)
|
||||
|
||||
|
||||
client = new_client()
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
import logging
|
||||
|
||||
from django.urls import reverse
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.views.generic.base import RedirectView
|
||||
|
@ -14,43 +13,35 @@ from django.http.response import (
|
|||
HttpResponseRedirect
|
||||
)
|
||||
|
||||
from . import client
|
||||
from .utils import new_client
|
||||
from .models import Nonce
|
||||
from users.models import LoginLog
|
||||
from users.tasks import write_login_log_async
|
||||
from common.utils import get_request_ip
|
||||
from .signals import post_openid_login_success
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
client = new_client()
|
||||
|
||||
__all__ = ['OpenIDLoginView', 'OpenIDLoginCompleteView']
|
||||
|
||||
|
||||
def get_base_site_url():
|
||||
return settings.BASE_SITE_URL
|
||||
|
||||
|
||||
class LoginView(RedirectView):
|
||||
class OpenIDLoginView(RedirectView):
|
||||
|
||||
def get_redirect_url(self, *args, **kwargs):
|
||||
redirect_uri = settings.BASE_SITE_URL + str(settings.LOGIN_COMPLETE_URL)
|
||||
nonce = Nonce(
|
||||
redirect_uri=get_base_site_url() + reverse(
|
||||
"authentication:openid-login-complete"),
|
||||
|
||||
redirect_uri=redirect_uri,
|
||||
next_path=self.request.GET.get('next')
|
||||
)
|
||||
|
||||
cache.set(str(nonce.state), nonce, 24*3600)
|
||||
|
||||
self.request.session['openid_state'] = str(nonce.state)
|
||||
|
||||
authorization_url = client.openid_connect_client.\
|
||||
authorization_url(
|
||||
redirect_uri=nonce.redirect_uri, scope='code',
|
||||
state=str(nonce.state)
|
||||
)
|
||||
|
||||
return authorization_url
|
||||
|
||||
|
||||
class LoginCompleteView(RedirectView):
|
||||
class OpenIDLoginCompleteView(RedirectView):
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
if 'error' in request.GET:
|
||||
|
@ -79,24 +70,8 @@ class LoginCompleteView(RedirectView):
|
|||
return HttpResponseBadRequest()
|
||||
|
||||
login(self.request, user)
|
||||
|
||||
data = {
|
||||
'username': user.username,
|
||||
'mfa': int(user.otp_enabled),
|
||||
'reason': LoginLog.REASON_NOTHING,
|
||||
'status': True
|
||||
}
|
||||
self.write_login_log(data)
|
||||
|
||||
post_openid_login_success.send(
|
||||
sender=self.__class__, user=user, request=self.request
|
||||
)
|
||||
return HttpResponseRedirect(nonce.next_path or '/')
|
||||
|
||||
def write_login_log(self, data):
|
||||
login_ip = get_request_ip(self.request)
|
||||
user_agent = self.request.META.get('HTTP_USER_AGENT', '')
|
||||
tmp_data = {
|
||||
'ip': login_ip,
|
||||
'type': 'W',
|
||||
'user_agent': user_agent
|
||||
}
|
||||
data.update(tmp_data)
|
||||
write_login_log_async.delay(**data)
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django import forms
|
||||
from django.contrib.auth.forms import AuthenticationForm
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from captcha.fields import CaptchaField
|
||||
|
||||
|
||||
class UserLoginForm(AuthenticationForm):
|
||||
username = forms.CharField(label=_('Username'), max_length=100)
|
||||
password = forms.CharField(
|
||||
label=_('Password'), widget=forms.PasswordInput,
|
||||
max_length=128, strip=False
|
||||
)
|
||||
|
||||
def confirm_login_allowed(self, user):
|
||||
if not user.is_staff:
|
||||
raise forms.ValidationError(
|
||||
self.error_messages['inactive'],
|
||||
code='inactive',)
|
||||
|
||||
|
||||
class UserLoginCaptchaForm(UserLoginForm):
|
||||
captcha = CaptchaField()
|
||||
|
||||
|
||||
class UserCheckOtpCodeForm(forms.Form):
|
||||
otp_code = forms.CharField(label=_('MFA code'), max_length=6)
|
|
@ -0,0 +1,51 @@
|
|||
# Generated by Django 2.1.7 on 2019-02-28 08:07
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AccessKey',
|
||||
fields=[
|
||||
('id',
|
||||
models.UUIDField(default=uuid.uuid4, editable=False,
|
||||
primary_key=True, serialize=False,
|
||||
verbose_name='AccessKeyID')),
|
||||
('secret',
|
||||
models.UUIDField(default=uuid.uuid4, editable=False,
|
||||
verbose_name='AccessKeySecret')),
|
||||
('user', models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='access_keys',
|
||||
to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PrivateToken',
|
||||
fields=[
|
||||
('key',
|
||||
models.CharField(max_length=40, primary_key=True,
|
||||
serialize=False, verbose_name='Key')),
|
||||
('created', models.DateTimeField(auto_now_add=True,
|
||||
verbose_name='Created')),
|
||||
('user', models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='auth_token',
|
||||
to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Private Token',
|
||||
},
|
||||
),
|
||||
]
|
|
@ -0,0 +1,2 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
|
@ -1 +1,33 @@
|
|||
import uuid
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework.authtoken.models import Token
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class AccessKey(models.Model):
|
||||
id = models.UUIDField(verbose_name='AccessKeyID', primary_key=True,
|
||||
default=uuid.uuid4, editable=False)
|
||||
secret = models.UUIDField(verbose_name='AccessKeySecret',
|
||||
default=uuid.uuid4, editable=False)
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name='User',
|
||||
on_delete=models.CASCADE, related_name='access_keys')
|
||||
|
||||
def get_id(self):
|
||||
return str(self.id)
|
||||
|
||||
def get_secret(self):
|
||||
return str(self.secret)
|
||||
|
||||
def get_full_value(self):
|
||||
return '{}:{}'.format(self.id, self.secret)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.id)
|
||||
|
||||
|
||||
class PrivateToken(Token):
|
||||
"""Inherit from auth token, otherwise migration is boring"""
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Private Token')
|
|
@ -0,0 +1,16 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework import serializers
|
||||
|
||||
from .models import AccessKey
|
||||
|
||||
|
||||
__all__ = ['AccessKeySerializer']
|
||||
|
||||
|
||||
class AccessKeySerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = AccessKey
|
||||
fields = ['id', 'secret']
|
||||
read_only_fields = ['id', 'secret']
|
|
@ -1,4 +1,5 @@
|
|||
from django.dispatch import Signal
|
||||
|
||||
|
||||
post_create_openid_user = Signal(providing_args=('user',))
|
||||
post_auth_success = Signal(providing_args=('user', 'request'))
|
||||
post_auth_failed = Signal(providing_args=('username', 'request', 'reason'))
|
||||
|
|
|
@ -2,9 +2,16 @@ from django.http.request import QueryDict
|
|||
from django.conf import settings
|
||||
from django.dispatch import receiver
|
||||
from django.contrib.auth.signals import user_logged_out
|
||||
from django.utils import timezone
|
||||
from django_auth_ldap.backend import populate_user
|
||||
from .openid import client
|
||||
from .signals import post_create_openid_user
|
||||
|
||||
from common.utils import get_request_ip
|
||||
from .backends.openid import new_client
|
||||
from .backends.openid.signals import (
|
||||
post_create_openid_user, post_openid_login_success
|
||||
)
|
||||
from .tasks import write_login_log_async
|
||||
from .signals import post_auth_success, post_auth_failed
|
||||
|
||||
|
||||
@receiver(user_logged_out)
|
||||
|
@ -17,6 +24,7 @@ def on_user_logged_out(sender, request, user, **kwargs):
|
|||
'redirect_uri': settings.BASE_SITE_URL
|
||||
})
|
||||
|
||||
client = new_client()
|
||||
openid_logout_url = "%s?%s" % (
|
||||
client.openid_connect_client.get_url(
|
||||
name='end_session_endpoint'),
|
||||
|
@ -33,8 +41,46 @@ def on_post_create_openid_user(sender, user=None, **kwargs):
|
|||
user.save()
|
||||
|
||||
|
||||
@receiver(post_openid_login_success)
|
||||
def on_openid_login_success(sender, user=None, request=None, **kwargs):
|
||||
post_auth_success.send(sender=sender, user=user, request=request)
|
||||
|
||||
|
||||
@receiver(populate_user)
|
||||
def on_ldap_create_user(sender, user, ldap_user, **kwargs):
|
||||
if user and user.name != 'admin':
|
||||
user.source = user.SOURCE_LDAP
|
||||
user.save()
|
||||
|
||||
|
||||
def generate_data(username, request):
|
||||
if not request.user.is_anonymous and request.user.is_app:
|
||||
login_ip = request.data.get('remote_addr', None)
|
||||
login_type = request.data.get('login_type', '')
|
||||
user_agent = request.data.get('HTTP_USER_AGENT', '')
|
||||
else:
|
||||
login_ip = get_request_ip(request)
|
||||
user_agent = request.META.get('HTTP_USER_AGENT', '')
|
||||
login_type = 'W'
|
||||
data = {
|
||||
'username': username,
|
||||
'ip': login_ip,
|
||||
'type': login_type,
|
||||
'user_agent': user_agent,
|
||||
'datetime': timezone.now()
|
||||
}
|
||||
return data
|
||||
|
||||
|
||||
@receiver(post_auth_success)
|
||||
def on_user_auth_success(sender, user, request, **kwargs):
|
||||
data = generate_data(user.username, request)
|
||||
data.update({'mfa': int(user.otp_enabled), 'status': True})
|
||||
write_login_log_async.delay(**data)
|
||||
|
||||
|
||||
@receiver(post_auth_failed)
|
||||
def on_user_auth_failed(sender, username, request, reason, **kwargs):
|
||||
data = generate_data(username, request)
|
||||
data.update({'reason': reason, 'status': False})
|
||||
write_login_log_async.delay(**data)
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from celery import shared_task
|
||||
|
||||
from .utils import write_login_log
|
||||
|
||||
|
||||
@shared_task
|
||||
def write_login_log_async(*args, **kwargs):
|
||||
write_login_log(*args, **kwargs)
|
|
@ -1 +1,20 @@
|
|||
# coding:utf-8
|
||||
#
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django.urls import path
|
||||
|
||||
from .. import api
|
||||
|
||||
app_name = 'authentication'
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
# path('token/', api.UserToken.as_view(), name='user-token'),
|
||||
path('auth/', api.UserAuthApi.as_view(), name='user-auth'),
|
||||
path('connection-token/',
|
||||
api.UserConnectionTokenApi.as_view(), name='connection-token'),
|
||||
path('otp/auth/', api.UserOtpAuthApi.as_view(), name='user-otp-auth'),
|
||||
]
|
||||
|
||||
|
|
|
@ -1,16 +1,20 @@
|
|||
# coding:utf-8
|
||||
#
|
||||
|
||||
from django.urls import path
|
||||
from authentication.openid import views
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django.urls import path, include
|
||||
|
||||
from .. import views
|
||||
|
||||
app_name = 'authentication'
|
||||
|
||||
urlpatterns = [
|
||||
# openid
|
||||
path('openid/login/', views.LoginView.as_view(), name='openid-login'),
|
||||
path('openid/login/complete/', views.LoginCompleteView.as_view(),
|
||||
name='openid-login-complete'),
|
||||
path('openid/', include(('authentication.backends.openid.urls', 'authentication'), namespace='openid')),
|
||||
|
||||
# other
|
||||
# login
|
||||
path('login/', views.UserLoginView.as_view(), name='login'),
|
||||
path('login/otp/', views.UserLoginOtpView.as_view(), name='login-otp'),
|
||||
path('logout/', views.UserLogoutView.as_view(), name='logout'),
|
||||
]
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.utils.translation import ugettext as _
|
||||
from common.utils import get_ip_city, validate_ip
|
||||
|
||||
|
||||
def write_login_log(*args, **kwargs):
|
||||
from audits.models import UserLoginLog
|
||||
default_city = _("Unknown")
|
||||
ip = kwargs.get('ip', '')
|
||||
if not (ip and validate_ip(ip)):
|
||||
ip = ip[:15]
|
||||
city = default_city
|
||||
else:
|
||||
city = get_ip_city(ip) or default_city
|
||||
kwargs.update({'ip': ip, 'city': city})
|
||||
UserLoginLog.objects.create(**kwargs)
|
||||
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from .login import *
|
|
@ -0,0 +1,208 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import os
|
||||
from django.core.cache import cache
|
||||
from django.contrib.auth import login as auth_login, logout as auth_logout
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import reverse, redirect
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.decorators.cache import never_cache
|
||||
from django.views.decorators.csrf import csrf_protect
|
||||
from django.views.decorators.debug import sensitive_post_parameters
|
||||
from django.views.generic.base import TemplateView
|
||||
from django.views.generic.edit import FormView
|
||||
from django.conf import settings
|
||||
|
||||
from common.utils import get_request_ip
|
||||
from users.models import User
|
||||
from audits.models import UserLoginLog as LoginLog
|
||||
from users.utils import (
|
||||
check_otp_code, is_block_login, clean_failed_count, get_user_or_tmp_user,
|
||||
set_tmp_user_to_cache, increase_login_failed_count,
|
||||
redirect_user_first_login_or_index,
|
||||
)
|
||||
from ..signals import post_auth_success, post_auth_failed
|
||||
from .. import forms
|
||||
|
||||
|
||||
__all__ = [
|
||||
'UserLoginView', 'UserLoginOtpView', 'UserLogoutView',
|
||||
]
|
||||
|
||||
|
||||
@method_decorator(sensitive_post_parameters(), name='dispatch')
|
||||
@method_decorator(csrf_protect, name='dispatch')
|
||||
@method_decorator(never_cache, name='dispatch')
|
||||
class UserLoginView(FormView):
|
||||
form_class = forms.UserLoginForm
|
||||
form_class_captcha = forms.UserLoginCaptchaForm
|
||||
redirect_field_name = 'next'
|
||||
key_prefix_captcha = "_LOGIN_INVALID_{}"
|
||||
|
||||
def get_template_names(self):
|
||||
template_name = 'users/login.html'
|
||||
if not settings.XPACK_ENABLED:
|
||||
return template_name
|
||||
|
||||
from xpack.plugins.license.models import License
|
||||
if not License.has_valid_license():
|
||||
return template_name
|
||||
|
||||
template_name = 'users/new_login.html'
|
||||
return template_name
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
if request.user.is_staff:
|
||||
return redirect(redirect_user_first_login_or_index(
|
||||
request, self.redirect_field_name)
|
||||
)
|
||||
request.session.set_test_cookie()
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
# limit login authentication
|
||||
ip = get_request_ip(request)
|
||||
username = self.request.POST.get('username')
|
||||
if is_block_login(username, ip):
|
||||
return self.render_to_response(self.get_context_data(block_login=True))
|
||||
return super().post(request, *args, **kwargs)
|
||||
|
||||
def form_valid(self, form):
|
||||
if not self.request.session.test_cookie_worked():
|
||||
return HttpResponse(_("Please enable cookies and try again."))
|
||||
user = form.get_user()
|
||||
# user password expired
|
||||
if user.password_has_expired:
|
||||
reason = LoginLog.REASON_PASSWORD_EXPIRED
|
||||
self.send_auth_signal(success=False, username=user.username, reason=reason)
|
||||
return self.render_to_response(self.get_context_data(password_expired=True))
|
||||
|
||||
set_tmp_user_to_cache(self.request, user)
|
||||
username = form.cleaned_data.get('username')
|
||||
ip = get_request_ip(self.request)
|
||||
# 登陆成功,清除缓存计数
|
||||
clean_failed_count(username, ip)
|
||||
return redirect(self.get_success_url())
|
||||
|
||||
def form_invalid(self, form):
|
||||
# write login failed log
|
||||
username = form.cleaned_data.get('username')
|
||||
exist = User.objects.filter(username=username).first()
|
||||
reason = LoginLog.REASON_PASSWORD if exist else LoginLog.REASON_NOT_EXIST
|
||||
# limit user login failed count
|
||||
ip = get_request_ip(self.request)
|
||||
increase_login_failed_count(username, ip)
|
||||
# show captcha
|
||||
cache.set(self.key_prefix_captcha.format(ip), 1, 3600)
|
||||
self.send_auth_signal(success=False, username=username, reason=reason)
|
||||
|
||||
old_form = form
|
||||
form = self.form_class_captcha(data=form.data)
|
||||
form._errors = old_form.errors
|
||||
return super().form_invalid(form)
|
||||
|
||||
def get_form_class(self):
|
||||
ip = get_request_ip(self.request)
|
||||
if cache.get(self.key_prefix_captcha.format(ip)):
|
||||
return self.form_class_captcha
|
||||
else:
|
||||
return self.form_class
|
||||
|
||||
def get_success_url(self):
|
||||
user = get_user_or_tmp_user(self.request)
|
||||
|
||||
if user.otp_enabled and user.otp_secret_key:
|
||||
# 1,2,mfa_setting & T
|
||||
return reverse('authentication:login-otp')
|
||||
elif user.otp_enabled and not user.otp_secret_key:
|
||||
# 1,2,mfa_setting & F
|
||||
return reverse('users:user-otp-enable-authentication')
|
||||
elif not user.otp_enabled:
|
||||
# 0 & T,F
|
||||
auth_login(self.request, user)
|
||||
self.send_auth_signal(success=True, user=user)
|
||||
return redirect_user_first_login_or_index(self.request, self.redirect_field_name)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'demo_mode': os.environ.get("DEMO_MODE"),
|
||||
'AUTH_OPENID': settings.AUTH_OPENID,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def send_auth_signal(self, success=True, user=None, username='', reason=''):
|
||||
if success:
|
||||
post_auth_success.send(sender=self.__class__, user=user, request=self.request)
|
||||
else:
|
||||
post_auth_failed.send(
|
||||
sender=self.__class__, username=username,
|
||||
request=self.request, reason=reason
|
||||
)
|
||||
|
||||
|
||||
class UserLoginOtpView(FormView):
|
||||
template_name = 'users/login_otp.html'
|
||||
form_class = forms.UserCheckOtpCodeForm
|
||||
redirect_field_name = 'next'
|
||||
|
||||
def form_valid(self, form):
|
||||
user = get_user_or_tmp_user(self.request)
|
||||
otp_code = form.cleaned_data.get('otp_code')
|
||||
otp_secret_key = user.otp_secret_key
|
||||
|
||||
if check_otp_code(otp_secret_key, otp_code):
|
||||
auth_login(self.request, user)
|
||||
self.send_auth_signal(success=True, user=user)
|
||||
return redirect(self.get_success_url())
|
||||
else:
|
||||
self.send_auth_signal(
|
||||
success=False, username=user.username,
|
||||
reason=LoginLog.REASON_MFA
|
||||
)
|
||||
form.add_error(
|
||||
'otp_code', _('MFA code invalid, or ntp sync server time')
|
||||
)
|
||||
return super().form_invalid(form)
|
||||
|
||||
def get_success_url(self):
|
||||
return redirect_user_first_login_or_index(self.request, self.redirect_field_name)
|
||||
|
||||
def send_auth_signal(self, success=True, user=None, username='', reason=''):
|
||||
if success:
|
||||
post_auth_success.send(sender=self.__class__, user=user, request=self.request)
|
||||
else:
|
||||
post_auth_failed.send(
|
||||
sender=self.__class__, username=username,
|
||||
request=self.request, reason=reason
|
||||
)
|
||||
|
||||
|
||||
@method_decorator(never_cache, name='dispatch')
|
||||
class UserLogoutView(TemplateView):
|
||||
template_name = 'flash_message_standalone.html'
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
auth_logout(request)
|
||||
next_uri = request.COOKIES.get("next")
|
||||
if next_uri:
|
||||
return redirect(next_uri)
|
||||
response = super().get(request, *args, **kwargs)
|
||||
return response
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'title': _('Logout success'),
|
||||
'messages': _('Logout success, return login page'),
|
||||
'interval': 1,
|
||||
'redirect_url': reverse('authentication:login'),
|
||||
'auto_redirect': True,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from .common import *
|
||||
from .django import *
|
||||
from .encode import *
|
||||
from .http import *
|
||||
from .ipip import *
|
|
@ -1,104 +1,18 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import re
|
||||
import sys
|
||||
from collections import OrderedDict
|
||||
from six import string_types
|
||||
import base64
|
||||
import os
|
||||
from itertools import chain
|
||||
import logging
|
||||
import datetime
|
||||
import time
|
||||
import hashlib
|
||||
from email.utils import formatdate
|
||||
import calendar
|
||||
import threading
|
||||
from io import StringIO
|
||||
import uuid
|
||||
from functools import wraps
|
||||
import copy
|
||||
|
||||
import paramiko
|
||||
import sshpubkeys
|
||||
from itsdangerous import TimedJSONWebSignatureSerializer, JSONWebSignatureSerializer, \
|
||||
BadSignature, SignatureExpired
|
||||
from django.shortcuts import reverse as dj_reverse
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
import ipaddress
|
||||
|
||||
|
||||
UUID_PATTERN = re.compile(r'[0-9a-zA-Z\-]{36}')
|
||||
|
||||
|
||||
def reverse(view_name, urlconf=None, args=None, kwargs=None,
|
||||
current_app=None, external=False):
|
||||
url = dj_reverse(view_name, urlconf=urlconf, args=args,
|
||||
kwargs=kwargs, current_app=current_app)
|
||||
|
||||
if external:
|
||||
site_url = settings.SITE_URL
|
||||
url = site_url.strip('/') + url
|
||||
return url
|
||||
|
||||
|
||||
def get_object_or_none(model, **kwargs):
|
||||
try:
|
||||
obj = model.objects.get(**kwargs)
|
||||
except model.DoesNotExist:
|
||||
return None
|
||||
return obj
|
||||
|
||||
|
||||
class Singleton(type):
|
||||
def __init__(cls, *args, **kwargs):
|
||||
cls.__instance = None
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def __call__(cls, *args, **kwargs):
|
||||
if cls.__instance is None:
|
||||
cls.__instance = super().__call__(*args, **kwargs)
|
||||
return cls.__instance
|
||||
else:
|
||||
return cls.__instance
|
||||
|
||||
|
||||
class Signer(metaclass=Singleton):
|
||||
"""用来加密,解密,和基于时间戳的方式验证token"""
|
||||
def __init__(self, secret_key=None):
|
||||
self.secret_key = secret_key
|
||||
|
||||
def sign(self, value):
|
||||
s = JSONWebSignatureSerializer(self.secret_key, algorithm_name='HS256')
|
||||
return s.dumps(value).decode()
|
||||
|
||||
def unsign(self, value):
|
||||
if value is None:
|
||||
return value
|
||||
s = JSONWebSignatureSerializer(self.secret_key, algorithm_name='HS256')
|
||||
try:
|
||||
return s.loads(value)
|
||||
except BadSignature:
|
||||
return {}
|
||||
|
||||
def sign_t(self, value, expires_in=3600):
|
||||
s = TimedJSONWebSignatureSerializer(self.secret_key, expires_in=expires_in)
|
||||
return str(s.dumps(value), encoding="utf8")
|
||||
|
||||
def unsign_t(self, value):
|
||||
s = TimedJSONWebSignatureSerializer(self.secret_key)
|
||||
try:
|
||||
return s.loads(value)
|
||||
except (BadSignature, SignatureExpired):
|
||||
return {}
|
||||
|
||||
|
||||
def date_expired_default():
|
||||
try:
|
||||
years = int(settings.DEFAULT_EXPIRED_YEARS)
|
||||
except TypeError:
|
||||
years = 70
|
||||
return timezone.now() + timezone.timedelta(days=365*years)
|
||||
ipip_db = None
|
||||
|
||||
|
||||
def combine_seq(s1, s2, callback=None):
|
||||
|
@ -146,88 +60,6 @@ def timesince(dt, since='', default="just now"):
|
|||
return default
|
||||
|
||||
|
||||
def ssh_key_string_to_obj(text, password=None):
|
||||
key = None
|
||||
try:
|
||||
key = paramiko.RSAKey.from_private_key(StringIO(text), password=password)
|
||||
except paramiko.SSHException:
|
||||
pass
|
||||
|
||||
try:
|
||||
key = paramiko.DSSKey.from_private_key(StringIO(text), password=password)
|
||||
except paramiko.SSHException:
|
||||
pass
|
||||
return key
|
||||
|
||||
|
||||
def ssh_pubkey_gen(private_key=None, username='jumpserver', hostname='localhost', password=None):
|
||||
if isinstance(private_key, bytes):
|
||||
private_key = private_key.decode("utf-8")
|
||||
if isinstance(private_key, string_types):
|
||||
private_key = ssh_key_string_to_obj(private_key, password=password)
|
||||
if not isinstance(private_key, (paramiko.RSAKey, paramiko.DSSKey)):
|
||||
raise IOError('Invalid private key')
|
||||
|
||||
public_key = "%(key_type)s %(key_content)s %(username)s@%(hostname)s" % {
|
||||
'key_type': private_key.get_name(),
|
||||
'key_content': private_key.get_base64(),
|
||||
'username': username,
|
||||
'hostname': hostname,
|
||||
}
|
||||
return public_key
|
||||
|
||||
|
||||
def ssh_key_gen(length=2048, type='rsa', password=None, username='jumpserver', hostname=None):
|
||||
"""Generate user ssh private and public key
|
||||
|
||||
Use paramiko RSAKey generate it.
|
||||
:return private key str and public key str
|
||||
"""
|
||||
|
||||
if hostname is None:
|
||||
hostname = os.uname()[1]
|
||||
|
||||
f = StringIO()
|
||||
try:
|
||||
if type == 'rsa':
|
||||
private_key_obj = paramiko.RSAKey.generate(length)
|
||||
elif type == 'dsa':
|
||||
private_key_obj = paramiko.DSSKey.generate(length)
|
||||
else:
|
||||
raise IOError('SSH private key must be `rsa` or `dsa`')
|
||||
private_key_obj.write_private_key(f, password=password)
|
||||
private_key = f.getvalue()
|
||||
public_key = ssh_pubkey_gen(private_key_obj, username=username, hostname=hostname)
|
||||
return private_key, public_key
|
||||
except IOError:
|
||||
raise IOError('These is error when generate ssh key.')
|
||||
|
||||
|
||||
def validate_ssh_private_key(text, password=None):
|
||||
if isinstance(text, bytes):
|
||||
try:
|
||||
text = text.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
return False
|
||||
|
||||
key = ssh_key_string_to_obj(text, password=password)
|
||||
if key is None:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def validate_ssh_public_key(text):
|
||||
ssh = sshpubkeys.SSHKey(text)
|
||||
try:
|
||||
ssh.parse()
|
||||
except (sshpubkeys.InvalidKeyException, UnicodeDecodeError):
|
||||
return False
|
||||
except NotImplementedError as e:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def setattr_bulk(seq, key, value):
|
||||
def set_attr(obj):
|
||||
setattr(obj, key, value)
|
||||
|
@ -243,70 +75,6 @@ def set_or_append_attr_bulk(seq, key, value):
|
|||
setattr(obj, key, value)
|
||||
|
||||
|
||||
def content_md5(data):
|
||||
"""计算data的MD5值,经过Base64编码并返回str类型。
|
||||
|
||||
返回值可以直接作为HTTP Content-Type头部的值
|
||||
"""
|
||||
if isinstance(data, str):
|
||||
data = hashlib.md5(data.encode('utf-8'))
|
||||
value = base64.b64encode(data.hexdigest().encode('utf-8'))
|
||||
return value.decode('utf-8')
|
||||
|
||||
|
||||
_STRPTIME_LOCK = threading.Lock()
|
||||
|
||||
_GMT_FORMAT = "%a, %d %b %Y %H:%M:%S GMT"
|
||||
_ISO8601_FORMAT = "%Y-%m-%dT%H:%M:%S.000Z"
|
||||
|
||||
|
||||
def to_unixtime(time_string, format_string):
|
||||
time_string = time_string.decode("ascii")
|
||||
with _STRPTIME_LOCK:
|
||||
return int(calendar.timegm(time.strptime(time_string, format_string)))
|
||||
|
||||
|
||||
def http_date(timeval=None):
|
||||
"""返回符合HTTP标准的GMT时间字符串,用strftime的格式表示就是"%a, %d %b %Y %H:%M:%S GMT"。
|
||||
但不能使用strftime,因为strftime的结果是和locale相关的。
|
||||
"""
|
||||
return formatdate(timeval, usegmt=True)
|
||||
|
||||
|
||||
def http_to_unixtime(time_string):
|
||||
"""把HTTP Date格式的字符串转换为UNIX时间(自1970年1月1日UTC零点的秒数)。
|
||||
|
||||
HTTP Date形如 `Sat, 05 Dec 2015 11:10:29 GMT` 。
|
||||
"""
|
||||
return to_unixtime(time_string, _GMT_FORMAT)
|
||||
|
||||
|
||||
def iso8601_to_unixtime(time_string):
|
||||
"""把ISO8601时间字符串(形如,2012-02-24T06:07:48.000Z)转换为UNIX时间,精确到秒。"""
|
||||
return to_unixtime(time_string, _ISO8601_FORMAT)
|
||||
|
||||
|
||||
def make_signature(access_key_secret, date=None):
|
||||
if isinstance(date, bytes):
|
||||
date = bytes.decode(date)
|
||||
if isinstance(date, int):
|
||||
date_gmt = http_date(date)
|
||||
elif date is None:
|
||||
date_gmt = http_date(int(time.time()))
|
||||
else:
|
||||
date_gmt = date
|
||||
|
||||
data = str(access_key_secret) + "\n" + date_gmt
|
||||
return content_md5(data)
|
||||
|
||||
|
||||
def encrypt_password(password, salt=None):
|
||||
from passlib.hash import sha512_crypt
|
||||
if password:
|
||||
return sha512_crypt.using(rounds=5000).hash(password, salt=salt)
|
||||
return None
|
||||
|
||||
|
||||
def capacity_convert(size, expect='auto', rate=1000):
|
||||
"""
|
||||
:param size: '100MB', '1G'
|
||||
|
@ -374,11 +142,6 @@ def is_uuid(seq):
|
|||
return True
|
||||
|
||||
|
||||
def get_signer():
|
||||
signer = Signer(settings.SECRET_KEY)
|
||||
return signer
|
||||
|
||||
|
||||
def get_request_ip(request):
|
||||
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR', '').split(',')
|
||||
if x_forwarded_for and x_forwarded_for[0]:
|
||||
|
@ -388,22 +151,13 @@ def get_request_ip(request):
|
|||
return login_ip
|
||||
|
||||
|
||||
def get_command_storage_setting():
|
||||
default = settings.DEFAULT_TERMINAL_COMMAND_STORAGE
|
||||
value = settings.TERMINAL_COMMAND_STORAGE
|
||||
if not value:
|
||||
return default
|
||||
value.update(default)
|
||||
return value
|
||||
|
||||
|
||||
def get_replay_storage_setting():
|
||||
default = settings.DEFAULT_TERMINAL_REPLAY_STORAGE
|
||||
value = settings.TERMINAL_REPLAY_STORAGE
|
||||
if not value:
|
||||
return default
|
||||
value.update(default)
|
||||
return value
|
||||
def validate_ip(ip):
|
||||
try:
|
||||
ipaddress.ip_address(ip)
|
||||
return True
|
||||
except ValueError:
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def with_cache(func):
|
||||
|
@ -537,4 +291,4 @@ class LocalProxy(object):
|
|||
__rmod__ = lambda x, o: o % x._get_current_object()
|
||||
__rdivmod__ = lambda x, o: x._get_current_object().__rdivmod__(o)
|
||||
__copy__ = lambda x: copy.copy(x._get_current_object())
|
||||
__deepcopy__ = lambda x, memo: copy.deepcopy(x._get_current_object(), memo)
|
||||
__deepcopy__ = lambda x, memo: copy.deepcopy(x._get_current_object(), memo)
|
|
@ -0,0 +1,54 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import re
|
||||
from django.shortcuts import reverse as dj_reverse
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
UUID_PATTERN = re.compile(r'[0-9a-zA-Z\-]{36}')
|
||||
|
||||
|
||||
def reverse(view_name, urlconf=None, args=None, kwargs=None,
|
||||
current_app=None, external=False):
|
||||
url = dj_reverse(view_name, urlconf=urlconf, args=args,
|
||||
kwargs=kwargs, current_app=current_app)
|
||||
|
||||
if external:
|
||||
site_url = settings.SITE_URL
|
||||
url = site_url.strip('/') + url
|
||||
return url
|
||||
|
||||
|
||||
def get_object_or_none(model, **kwargs):
|
||||
try:
|
||||
obj = model.objects.get(**kwargs)
|
||||
except model.DoesNotExist:
|
||||
return None
|
||||
return obj
|
||||
|
||||
|
||||
def date_expired_default():
|
||||
try:
|
||||
years = int(settings.DEFAULT_EXPIRED_YEARS)
|
||||
except TypeError:
|
||||
years = 70
|
||||
return timezone.now() + timezone.timedelta(days=365*years)
|
||||
|
||||
|
||||
def get_command_storage_setting():
|
||||
default = settings.DEFAULT_TERMINAL_COMMAND_STORAGE
|
||||
value = settings.TERMINAL_COMMAND_STORAGE
|
||||
if not value:
|
||||
return default
|
||||
value.update(default)
|
||||
return value
|
||||
|
||||
|
||||
def get_replay_storage_setting():
|
||||
default = settings.DEFAULT_TERMINAL_REPLAY_STORAGE
|
||||
value = settings.TERMINAL_REPLAY_STORAGE
|
||||
if not value:
|
||||
return default
|
||||
value.update(default)
|
||||
return value
|
|
@ -0,0 +1,184 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import re
|
||||
from six import string_types
|
||||
import base64
|
||||
import os
|
||||
import time
|
||||
import hashlib
|
||||
from io import StringIO
|
||||
|
||||
import paramiko
|
||||
import sshpubkeys
|
||||
from itsdangerous import (
|
||||
TimedJSONWebSignatureSerializer, JSONWebSignatureSerializer,
|
||||
BadSignature, SignatureExpired
|
||||
)
|
||||
from django.conf import settings
|
||||
|
||||
from .http import http_date
|
||||
|
||||
|
||||
UUID_PATTERN = re.compile(r'[0-9a-zA-Z\-]{36}')
|
||||
|
||||
|
||||
class Singleton(type):
|
||||
def __init__(cls, *args, **kwargs):
|
||||
cls.__instance = None
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def __call__(cls, *args, **kwargs):
|
||||
if cls.__instance is None:
|
||||
cls.__instance = super().__call__(*args, **kwargs)
|
||||
return cls.__instance
|
||||
else:
|
||||
return cls.__instance
|
||||
|
||||
|
||||
class Signer(metaclass=Singleton):
|
||||
"""用来加密,解密,和基于时间戳的方式验证token"""
|
||||
def __init__(self, secret_key=None):
|
||||
self.secret_key = secret_key
|
||||
|
||||
def sign(self, value):
|
||||
s = JSONWebSignatureSerializer(self.secret_key, algorithm_name='HS256')
|
||||
return s.dumps(value).decode()
|
||||
|
||||
def unsign(self, value):
|
||||
if value is None:
|
||||
return value
|
||||
s = JSONWebSignatureSerializer(self.secret_key, algorithm_name='HS256')
|
||||
try:
|
||||
return s.loads(value)
|
||||
except BadSignature:
|
||||
return {}
|
||||
|
||||
def sign_t(self, value, expires_in=3600):
|
||||
s = TimedJSONWebSignatureSerializer(self.secret_key, expires_in=expires_in)
|
||||
return str(s.dumps(value), encoding="utf8")
|
||||
|
||||
def unsign_t(self, value):
|
||||
s = TimedJSONWebSignatureSerializer(self.secret_key)
|
||||
try:
|
||||
return s.loads(value)
|
||||
except (BadSignature, SignatureExpired):
|
||||
return {}
|
||||
|
||||
|
||||
def ssh_key_string_to_obj(text, password=None):
|
||||
key = None
|
||||
try:
|
||||
key = paramiko.RSAKey.from_private_key(StringIO(text), password=password)
|
||||
except paramiko.SSHException:
|
||||
pass
|
||||
|
||||
try:
|
||||
key = paramiko.DSSKey.from_private_key(StringIO(text), password=password)
|
||||
except paramiko.SSHException:
|
||||
pass
|
||||
return key
|
||||
|
||||
|
||||
def ssh_pubkey_gen(private_key=None, username='jumpserver', hostname='localhost', password=None):
|
||||
if isinstance(private_key, bytes):
|
||||
private_key = private_key.decode("utf-8")
|
||||
if isinstance(private_key, string_types):
|
||||
private_key = ssh_key_string_to_obj(private_key, password=password)
|
||||
if not isinstance(private_key, (paramiko.RSAKey, paramiko.DSSKey)):
|
||||
raise IOError('Invalid private key')
|
||||
|
||||
public_key = "%(key_type)s %(key_content)s %(username)s@%(hostname)s" % {
|
||||
'key_type': private_key.get_name(),
|
||||
'key_content': private_key.get_base64(),
|
||||
'username': username,
|
||||
'hostname': hostname,
|
||||
}
|
||||
return public_key
|
||||
|
||||
|
||||
def ssh_key_gen(length=2048, type='rsa', password=None, username='jumpserver', hostname=None):
|
||||
"""Generate user ssh private and public key
|
||||
|
||||
Use paramiko RSAKey generate it.
|
||||
:return private key str and public key str
|
||||
"""
|
||||
|
||||
if hostname is None:
|
||||
hostname = os.uname()[1]
|
||||
|
||||
f = StringIO()
|
||||
try:
|
||||
if type == 'rsa':
|
||||
private_key_obj = paramiko.RSAKey.generate(length)
|
||||
elif type == 'dsa':
|
||||
private_key_obj = paramiko.DSSKey.generate(length)
|
||||
else:
|
||||
raise IOError('SSH private key must be `rsa` or `dsa`')
|
||||
private_key_obj.write_private_key(f, password=password)
|
||||
private_key = f.getvalue()
|
||||
public_key = ssh_pubkey_gen(private_key_obj, username=username, hostname=hostname)
|
||||
return private_key, public_key
|
||||
except IOError:
|
||||
raise IOError('These is error when generate ssh key.')
|
||||
|
||||
|
||||
def validate_ssh_private_key(text, password=None):
|
||||
if isinstance(text, bytes):
|
||||
try:
|
||||
text = text.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
return False
|
||||
|
||||
key = ssh_key_string_to_obj(text, password=password)
|
||||
if key is None:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def validate_ssh_public_key(text):
|
||||
ssh = sshpubkeys.SSHKey(text)
|
||||
try:
|
||||
ssh.parse()
|
||||
except (sshpubkeys.InvalidKeyException, UnicodeDecodeError):
|
||||
return False
|
||||
except NotImplementedError as e:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def content_md5(data):
|
||||
"""计算data的MD5值,经过Base64编码并返回str类型。
|
||||
|
||||
返回值可以直接作为HTTP Content-Type头部的值
|
||||
"""
|
||||
if isinstance(data, str):
|
||||
data = hashlib.md5(data.encode('utf-8'))
|
||||
value = base64.b64encode(data.hexdigest().encode('utf-8'))
|
||||
return value.decode('utf-8')
|
||||
|
||||
|
||||
def make_signature(access_key_secret, date=None):
|
||||
if isinstance(date, bytes):
|
||||
date = bytes.decode(date)
|
||||
if isinstance(date, int):
|
||||
date_gmt = http_date(date)
|
||||
elif date is None:
|
||||
date_gmt = http_date(int(time.time()))
|
||||
else:
|
||||
date_gmt = date
|
||||
|
||||
data = str(access_key_secret) + "\n" + date_gmt
|
||||
return content_md5(data)
|
||||
|
||||
|
||||
def encrypt_password(password, salt=None):
|
||||
from passlib.hash import sha512_crypt
|
||||
if password:
|
||||
return sha512_crypt.using(rounds=5000).hash(password, salt=salt)
|
||||
return None
|
||||
|
||||
|
||||
def get_signer():
|
||||
signer = Signer(settings.SECRET_KEY)
|
||||
return signer
|
|
@ -0,0 +1,37 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import time
|
||||
from email.utils import formatdate
|
||||
import calendar
|
||||
import threading
|
||||
|
||||
_STRPTIME_LOCK = threading.Lock()
|
||||
|
||||
_GMT_FORMAT = "%a, %d %b %Y %H:%M:%S GMT"
|
||||
_ISO8601_FORMAT = "%Y-%m-%dT%H:%M:%S.000Z"
|
||||
|
||||
|
||||
def to_unixtime(time_string, format_string):
|
||||
time_string = time_string.decode("ascii")
|
||||
with _STRPTIME_LOCK:
|
||||
return int(calendar.timegm(time.strptime(time_string, format_string)))
|
||||
|
||||
|
||||
def http_date(timeval=None):
|
||||
"""返回符合HTTP标准的GMT时间字符串,用strftime的格式表示就是"%a, %d %b %Y %H:%M:%S GMT"。
|
||||
但不能使用strftime,因为strftime的结果是和locale相关的。
|
||||
"""
|
||||
return formatdate(timeval, usegmt=True)
|
||||
|
||||
|
||||
def http_to_unixtime(time_string):
|
||||
"""把HTTP Date格式的字符串转换为UNIX时间(自1970年1月1日UTC零点的秒数)。
|
||||
|
||||
HTTP Date形如 `Sat, 05 Dec 2015 11:10:29 GMT` 。
|
||||
"""
|
||||
return to_unixtime(time_string, _GMT_FORMAT)
|
||||
|
||||
|
||||
def iso8601_to_unixtime(time_string):
|
||||
"""把ISO8601时间字符串(形如,2012-02-24T06:07:48.000Z)转换为UNIX时间,精确到秒。"""
|
||||
return to_unixtime(time_string, _ISO8601_FORMAT)
|
|
@ -0,0 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from .ipdb import *
|
|
@ -0,0 +1,18 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import os
|
||||
|
||||
import ipdb
|
||||
|
||||
ipip_db = None
|
||||
|
||||
|
||||
def get_ip_city(ip):
|
||||
global ipip_db
|
||||
if ipip_db is None:
|
||||
ipip_db_path = os.path.join(os.path.dirname(__file__), 'ipipfree.ipdb')
|
||||
ipip_db = ipdb.City(ipip_db_path)
|
||||
info = list(set(ipip_db.find(ip, 'CN')))
|
||||
if '' in info:
|
||||
info.remove('')
|
||||
return ' '.join(info)
|
Binary file not shown.
File diff suppressed because one or more lines are too long
|
@ -1 +0,0 @@
|
|||
[{"model": "users.usergroup", "pk": "f3c6b021-59a9-43e7-b022-c2e9bfac84d7", "fields": {"is_discard": false, "discard_time": null, "name": "Default", "comment": "Default user group", "date_created": "2017-12-12T08:13:20.906Z", "created_by": "System"}}, {"model": "users.loginlog", "pk": "328c7d0f-214d-4665-8c7b-f3063718530e", "fields": {"username": "admin", "type": "ST", "ip": "127.0.0.1", "city": "Unknown", "user_agent": "", "datetime": "2017-12-12T08:10:28.973Z"}}, {"model": "users.loginlog", "pk": "a72fa02e-3b2c-40a0-8cb1-0c2f98d8f248", "fields": {"username": "admin", "type": "ST", "ip": "127.0.0.1", "city": "Unknown", "user_agent": "", "datetime": "2017-12-12T08:10:28.980Z"}}, {"model": "assets.cluster", "pk": "a950b8aa-073b-45ab-b72e-5bdfbb614653", "fields": {"name": "Default", "admin_user": null, "bandwidth": "", "contact": "", "phone": "", "address": "", "intranet": "", "extranet": "", "date_created": "2017-12-12T08:13:20.919Z", "operator": "", "created_by": "System", "comment": "Default Cluster"}}, {"model": "assets.assetgroup", "pk": "d742a7be-faf1-4c29-ae0a-e6aa640ab395", "fields": {"name": "Default", "created_by": "", "date_created": "2017-12-12T08:13:20.923Z", "comment": "Default asset group"}}, {"model": "captcha.captchastore", "pk": 1, "fields": {"challenge": "EQEI", "response": "eqei", "hashkey": "c993e8e245252eb8ca40a57c67a63ee9c61dce5c", "expiration": "2017-12-12T08:17:50.235Z"}}, {"model": "users.user", "pk": "61c39c1f-5b57-4268-8180-b6dda235aadd", "fields": {"password": "pbkdf2_sha256$36000$yhYWUEo4DNqj$SpxtdIOm9nwRG+X76jUUlGvdDcLaMBl7Z+rJ8sfSMcU=", "last_login": null, "first_name": "", "last_name": "", "is_active": true, "date_joined": "2017-12-12T08:13:20.827Z", "username": "admin", "name": "Administrator", "email": "admin@jumpserver.org", "role": "Admin", "avatar": "", "wechat": "", "phone": null, "enable_otp": false, "secret_key_otp": "", "_private_key": "", "_public_key": "", "comment": "Administrator is the super user of system", "is_first_login": false, "date_expired": "2087-11-25T08:13:20.827Z", "created_by": "System", "user_permissions": [], "groups": ["f3c6b021-59a9-43e7-b022-c2e9bfac84d7"]}}]
|
|
@ -100,7 +100,7 @@ MIDDLEWARE = [
|
|||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'authentication.openid.middleware.OpenIDAuthenticationMiddleware', # openid
|
||||
'authentication.backends.openid.middleware.OpenIDAuthenticationMiddleware',
|
||||
'jumpserver.middleware.TimezoneMiddleware',
|
||||
'jumpserver.middleware.DemoMiddleware',
|
||||
'jumpserver.middleware.RequestMiddleware',
|
||||
|
@ -135,7 +135,7 @@ TEMPLATES = [
|
|||
# WSGI_APPLICATION = 'jumpserver.wsgi.applications'
|
||||
|
||||
LOGIN_REDIRECT_URL = reverse_lazy('index')
|
||||
LOGIN_URL = reverse_lazy('users:login')
|
||||
LOGIN_URL = reverse_lazy('authentication:login')
|
||||
|
||||
SESSION_COOKIE_DOMAIN = CONFIG.SESSION_COOKIE_DOMAIN
|
||||
CSRF_COOKIE_DOMAIN = CONFIG.CSRF_COOKIE_DOMAIN
|
||||
|
@ -343,10 +343,10 @@ REST_FRAMEWORK = {
|
|||
),
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||
# 'rest_framework.authentication.BasicAuthentication',
|
||||
'users.authentication.AccessKeyAuthentication',
|
||||
'users.authentication.AccessTokenAuthentication',
|
||||
'users.authentication.PrivateTokenAuthentication',
|
||||
'users.authentication.SessionAuthentication',
|
||||
'authentication.backends.api.AccessKeyAuthentication',
|
||||
'authentication.backends.api.AccessTokenAuthentication',
|
||||
'authentication.backends.api.PrivateTokenAuthentication',
|
||||
'authentication.backends.api.SessionAuthentication',
|
||||
),
|
||||
'DEFAULT_FILTER_BACKENDS': (
|
||||
'django_filters.rest_framework.DjangoFilterBackend',
|
||||
|
@ -395,7 +395,7 @@ AUTH_LDAP_CONNECTION_OPTIONS = {
|
|||
}
|
||||
AUTH_LDAP_GROUP_CACHE_TIMEOUT = 1
|
||||
AUTH_LDAP_ALWAYS_UPDATE_USER = True
|
||||
AUTH_LDAP_BACKEND = 'authentication.ldap.backends.LDAPAuthorizationBackend'
|
||||
AUTH_LDAP_BACKEND = 'authentication.backends.ldap.LDAPAuthorizationBackend'
|
||||
|
||||
if AUTH_LDAP:
|
||||
AUTHENTICATION_BACKENDS.insert(0, AUTH_LDAP_BACKEND)
|
||||
|
@ -409,18 +409,19 @@ AUTH_OPENID_REALM_NAME = CONFIG.AUTH_OPENID_REALM_NAME
|
|||
AUTH_OPENID_CLIENT_ID = CONFIG.AUTH_OPENID_CLIENT_ID
|
||||
AUTH_OPENID_CLIENT_SECRET = CONFIG.AUTH_OPENID_CLIENT_SECRET
|
||||
AUTH_OPENID_BACKENDS = [
|
||||
'authentication.openid.backends.OpenIDAuthorizationPasswordBackend',
|
||||
'authentication.openid.backends.OpenIDAuthorizationCodeBackend',
|
||||
'authentication.backends.openid.backends.OpenIDAuthorizationPasswordBackend',
|
||||
'authentication.backends.openid.backends.OpenIDAuthorizationCodeBackend',
|
||||
]
|
||||
|
||||
if AUTH_OPENID:
|
||||
LOGIN_URL = reverse_lazy("authentication:openid-login")
|
||||
LOGIN_URL = reverse_lazy("authentication:openid:openid-login")
|
||||
LOGIN_COMPLETE_URL = reverse_lazy("authentication:openid:openid-login-complete")
|
||||
AUTHENTICATION_BACKENDS.insert(0, AUTH_OPENID_BACKENDS[0])
|
||||
AUTHENTICATION_BACKENDS.insert(0, AUTH_OPENID_BACKENDS[1])
|
||||
|
||||
# Radius Auth
|
||||
AUTH_RADIUS = CONFIG.AUTH_RADIUS
|
||||
AUTH_RADIUS_BACKEND = 'authentication.radius.backends.RadiusBackend'
|
||||
AUTH_RADIUS_BACKEND = 'authentication.backends.radius.RadiusBackend'
|
||||
RADIUS_SERVER = CONFIG.RADIUS_SERVER
|
||||
RADIUS_PORT = CONFIG.RADIUS_PORT
|
||||
RADIUS_SECRET = CONFIG.RADIUS_SECRET
|
||||
|
|
|
@ -21,6 +21,7 @@ api_v1_patterns = [
|
|||
path('audits/v1/', include('audits.urls.api_urls', namespace='api-audits')),
|
||||
path('orgs/v1/', include('orgs.urls.api_urls', namespace='api-orgs')),
|
||||
path('settings/v1/', include('settings.urls.api_urls', namespace='api-settings')),
|
||||
path('authentication/v1/', include('authentication.urls.api_urls', namespace='api-auth')),
|
||||
]))
|
||||
]
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ def on_org_create_or_update(sender, instance=None, created=False, **kwargs):
|
|||
set_current_org(old_org)
|
||||
|
||||
if instance and not created:
|
||||
instance.expire_cache()
|
||||
instance.expire_user_cache()
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=Organization.users.through)
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
# Generated by Django 2.1.7 on 2019-02-28 10:21
|
||||
|
||||
import common.utils.django
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import uuid
|
||||
|
||||
|
||||
# Functions from the following migrations need manual copying.
|
||||
# Move them and any dependencies into this file, then update the
|
||||
# RunPython operations to refer to the local versions:
|
||||
# perms.migrations.0005_migrate_data_20180411_1144
|
||||
|
||||
def migrate_node_permissions(apps, schema_editor):
|
||||
node_perm_model = apps.get_model("perms", "NodePermission")
|
||||
asset_perm_model = apps.get_model("perms", "AssetPermission")
|
||||
db_alias = schema_editor.connection.alias
|
||||
for old in node_perm_model.objects.using(db_alias).all():
|
||||
perm = asset_perm_model.objects.using(db_alias).create(
|
||||
name="{}-{}-{}".format(
|
||||
old.node.value,
|
||||
old.user_group.name,
|
||||
old.system_user.name
|
||||
),
|
||||
is_active=old.is_active,
|
||||
date_expired=old.date_expired,
|
||||
created_by=old.date_expired,
|
||||
date_created=old.date_created,
|
||||
comment=old.comment,
|
||||
)
|
||||
perm.user_groups.add(old.user_group)
|
||||
perm.nodes.add(old.node)
|
||||
perm.system_users.add(old.system_user)
|
||||
|
||||
|
||||
def migrate_system_assets_relation(apps, schema_editor):
|
||||
system_user_model = apps.get_model("assets", "SystemUser")
|
||||
db_alias = schema_editor.connection.alias
|
||||
for s in system_user_model.objects.using(db_alias).all():
|
||||
nodes = list(s.nodes.all())
|
||||
s.nodes.set([])
|
||||
s.nodes.set(nodes)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('perms', '0002_auto_20171228_0025'), ('perms', '0003_auto_20180225_1815'), ('perms', '0004_auto_20180411_1135'), ('perms', '0005_migrate_data_20180411_1144'), ('perms', '0006_auto_20180606_1505'), ('perms', '0007_auto_20180807_1116'), ('perms', '0008_auto_20180816_1652'), ('perms', '0009_auto_20180903_1132')]
|
||||
|
||||
dependencies = [
|
||||
('users', '0002_auto_20171225_1157'),
|
||||
('assets', '0007_auto_20180225_1815'),
|
||||
('assets', '0013_auto_20180411_1135'),
|
||||
('users', '0004_auto_20180125_1218'),
|
||||
('perms', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='assetpermission',
|
||||
name='user_groups',
|
||||
field=models.ManyToManyField(blank=True, related_name='asset_permissions', to='users.UserGroup', verbose_name='User group'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='assetpermission',
|
||||
name='users',
|
||||
field=models.ManyToManyField(blank=True, related_name='asset_permissions', to=settings.AUTH_USER_MODEL, verbose_name='User'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='NodePermission',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='Active')),
|
||||
('date_expired', models.DateTimeField(default=common.utils.django.date_expired_default, verbose_name='Date expired')),
|
||||
('created_by', models.CharField(blank=True, max_length=128, verbose_name='Created by')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')),
|
||||
('comment', models.TextField(blank=True, verbose_name='Comment')),
|
||||
('node', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Node', verbose_name='Node')),
|
||||
('system_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.SystemUser', verbose_name='System user')),
|
||||
('user_group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.UserGroup', verbose_name='User group')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Asset permission',
|
||||
},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='nodepermission',
|
||||
unique_together={('node', 'user_group', 'system_user')},
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='assetpermission',
|
||||
name='asset_groups',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='assetpermission',
|
||||
name='date_start',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Date start'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='assetpermission',
|
||||
name='nodes',
|
||||
field=models.ManyToManyField(blank=True, related_name='granted_by_permissions', to='assets.Node', verbose_name='Nodes'),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=migrate_node_permissions,
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=migrate_system_assets_relation,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='assetpermission',
|
||||
name='date_expired',
|
||||
field=models.DateTimeField(db_index=True, default=common.utils.django.date_expired_default, verbose_name='Date expired'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='assetpermission',
|
||||
name='date_start',
|
||||
field=models.DateTimeField(db_index=True, default=django.utils.timezone.now, verbose_name='Date start'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='assetpermission',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='nodepermission',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='assetpermission',
|
||||
name='name',
|
||||
field=models.CharField(max_length=128, verbose_name='Name'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='assetpermission',
|
||||
unique_together={('org_id', 'name')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='nodepermission',
|
||||
unique_together=set(),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='assetpermission',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='nodepermission',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='assetpermission',
|
||||
options={'verbose_name': 'Asset permission'},
|
||||
),
|
||||
]
|
|
@ -94,10 +94,10 @@
|
|||
<li><a id="switch_user"><i class="fa fa-exchange"></i><span> {% trans 'User page' %}</span></a></li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<li><a href="{% url 'users:logout' %}"><i class="fa fa-sign-out"></i> {% trans 'Logout' %}</a></li>
|
||||
<li><a href="{% url 'authentication:logout' %}"><i class="fa fa-sign-out"></i> {% trans 'Logout' %}</a></li>
|
||||
</ul>
|
||||
{% else %}
|
||||
<a href="{% url 'users:login' %}">
|
||||
<a href="{% url 'authentication:login' %}">
|
||||
<i class="fa fa-sign-in"></i>{% trans 'Login' %}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
# Generated by Django 2.1.7 on 2019-02-28 10:23
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('terminal', '0002_auto_20171228_0025'), ('terminal', '0003_auto_20171230_0308'), ('terminal', '0004_session_remote_addr'), ('terminal', '0005_auto_20180122_1154'), ('terminal', '0006_auto_20180123_1037'), ('terminal', '0007_session_date_last_active'), ('terminal', '0008_auto_20180307_1603'), ('terminal', '0009_auto_20180326_0957')]
|
||||
|
||||
dependencies = [
|
||||
('terminal', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='session',
|
||||
name='terminal',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='terminal.Terminal'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='status',
|
||||
name='terminal',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='terminal.Terminal'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='task',
|
||||
name='terminal',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='terminal.Terminal'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='terminal',
|
||||
name='user',
|
||||
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='terminal', to=settings.AUTH_USER_MODEL, verbose_name='Application User'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='terminal',
|
||||
name='name',
|
||||
field=models.CharField(max_length=32, verbose_name='Name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='command',
|
||||
name='asset',
|
||||
field=models.CharField(db_index=True, max_length=128, verbose_name='Asset'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='command',
|
||||
name='system_user',
|
||||
field=models.CharField(db_index=True, max_length=64, verbose_name='System user'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='command',
|
||||
name='user',
|
||||
field=models.CharField(db_index=True, max_length=64, verbose_name='User'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='session',
|
||||
name='remote_addr',
|
||||
field=models.CharField(blank=True, max_length=15, null=True, verbose_name='Remote addr'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='terminal',
|
||||
name='command_storage',
|
||||
field=models.CharField(default='default', max_length=128, verbose_name='Command storage'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='terminal',
|
||||
name='replay_storage',
|
||||
field=models.CharField(default='default', max_length=128, verbose_name='Replay storage'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='session',
|
||||
name='date_last_active',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Date last active'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='session',
|
||||
name='date_start',
|
||||
field=models.DateTimeField(db_index=True, verbose_name='Date start'),
|
||||
),
|
||||
]
|
|
@ -2,5 +2,4 @@
|
|||
#
|
||||
|
||||
from .user import *
|
||||
from .auth import *
|
||||
from .group import *
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
|
||||
from django import forms
|
||||
from django.contrib.auth.forms import AuthenticationForm
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from captcha.fields import CaptchaField
|
||||
|
||||
from common.utils import validate_ssh_public_key
|
||||
from orgs.mixins import OrgModelForm
|
||||
|
@ -11,24 +9,6 @@ from orgs.utils import current_org
|
|||
from .models import User, UserGroup
|
||||
|
||||
|
||||
class UserLoginForm(AuthenticationForm):
|
||||
username = forms.CharField(label=_('Username'), max_length=100)
|
||||
password = forms.CharField(
|
||||
label=_('Password'), widget=forms.PasswordInput,
|
||||
max_length=128, strip=False
|
||||
)
|
||||
|
||||
def confirm_login_allowed(self, user):
|
||||
if not user.is_staff:
|
||||
raise forms.ValidationError(
|
||||
self.error_messages['inactive'],
|
||||
code='inactive',)
|
||||
|
||||
|
||||
class UserLoginCaptchaForm(UserLoginForm):
|
||||
captcha = CaptchaField()
|
||||
|
||||
|
||||
class UserCheckPasswordForm(forms.Form):
|
||||
username = forms.CharField(label=_('Username'), max_length=100)
|
||||
password = forms.CharField(
|
||||
|
|
|
@ -4,10 +4,8 @@ from __future__ import unicode_literals
|
|||
|
||||
import common.utils
|
||||
from django.contrib.auth.hashers import make_password
|
||||
from django.conf import settings
|
||||
import django.contrib.auth.models
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import uuid
|
||||
|
||||
|
@ -75,40 +73,6 @@ class Migration(migrations.Migration):
|
|||
('objects', django.contrib.auth.models.UserManager()),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AccessKey',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, verbose_name='AccessKeyID')),
|
||||
('secret', models.UUIDField(default=uuid.uuid4, editable=False, verbose_name='AccessKeySecret')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='access_key', to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='LoginLog',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('username', models.CharField(max_length=20, verbose_name='Username')),
|
||||
('type', models.CharField(choices=[('W', 'Web'), ('T', 'Terminal')], max_length=2, verbose_name='Login type')),
|
||||
('ip', models.GenericIPAddressField(verbose_name='Login ip')),
|
||||
('city', models.CharField(blank=True, max_length=254, null=True, verbose_name='Login city')),
|
||||
('user_agent', models.CharField(blank=True, max_length=254, null=True, verbose_name='User agent')),
|
||||
('datetime', models.DateTimeField(auto_now_add=True, verbose_name='Date login')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-datetime', 'username'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PrivateToken',
|
||||
fields=[
|
||||
('key', models.CharField(max_length=40, primary_key=True, serialize=False, verbose_name='Key')),
|
||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
|
||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='auth_token', to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Private Token',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UserGroup',
|
||||
fields=[
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
# Generated by Django 2.1.7 on 2019-02-28 10:19
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('users', '0002_auto_20171225_1157'), ('users', '0003_auto_20180101_0046'), ('users', '0004_auto_20180125_1218'), ('users', '0005_auto_20180306_1804'), ('users', '0006_auto_20180411_1135'), ('users', '0007_auto_20180419_1036'), ('users', '0008_auto_20180425_1516'), ('users', '0009_auto_20180517_1537')]
|
||||
|
||||
dependencies = [
|
||||
('users', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='email',
|
||||
field=models.EmailField(max_length=128, unique=True, verbose_name='Email'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='name',
|
||||
field=models.CharField(max_length=128, verbose_name='Name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='username',
|
||||
field=models.CharField(max_length=128, unique=True, verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='wechat',
|
||||
field=models.CharField(blank=True, max_length=128, verbose_name='Wechat'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='is_first_login',
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='usergroup',
|
||||
name='created_by',
|
||||
field=models.CharField(blank=True, max_length=100, null=True),
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='user',
|
||||
options={'ordering': ['username'], 'verbose_name': 'User'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='usergroup',
|
||||
options={'ordering': ['name'], 'verbose_name': 'User group'},
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='user',
|
||||
old_name='secret_key_otp',
|
||||
new_name='otp_secret_key',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='user',
|
||||
name='enable_otp',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='otp_level',
|
||||
field=models.SmallIntegerField(choices=[(0, 'Disable'), (1, 'Enable'), (2, 'Force enable')], default=0, verbose_name='MFA'),
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='user',
|
||||
name='otp_secret_key',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='_otp_secret_key',
|
||||
field=models.CharField(blank=True, max_length=128, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='usergroup',
|
||||
name='name',
|
||||
field=models.CharField(max_length=128, unique=True, verbose_name='Name'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,77 @@
|
|||
# Generated by Django 2.1.7 on 2019-02-28 10:20
|
||||
|
||||
import common.utils.django
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
# Functions from the following migrations need manual copying.
|
||||
# Move them and any dependencies into this file, then update the
|
||||
# RunPython operations to refer to the local versions:
|
||||
# users.migrations.0010_auto_20180606_1505
|
||||
|
||||
def remove_deleted_group(apps, schema_editor):
|
||||
db_alias = schema_editor.connection.alias
|
||||
group_model = apps.get_model("users", "UserGroup")
|
||||
group_model.objects.using(db_alias).filter(is_discard=True).delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('users', '0010_auto_20180606_1505'), ('users', '0011_user_source'), ('users', '0012_auto_20180710_1641'), ('users', '0013_auto_20180807_1116'), ('users', '0014_auto_20180816_1652'), ('users', '0015_auto_20181105_1112'), ('users', '0016_auto_20181109_1505'), ('users', '0017_auto_20181123_1113'), ('users', '0018_auto_20190107_1912')]
|
||||
|
||||
dependencies = [
|
||||
('users', '0009_auto_20180517_1537'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
code=remove_deleted_group,
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='usergroup',
|
||||
name='discard_time',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='usergroup',
|
||||
name='is_discard',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='date_expired',
|
||||
field=models.DateTimeField(blank=True, db_index=True, default=common.utils.django.date_expired_default, null=True, verbose_name='Date expired'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='source',
|
||||
field=models.CharField(choices=[('local', 'Local'), ('ldap', 'LDAP/AD'), ('openid', 'OpenID'), ('radius', 'Radius')], default='local', max_length=30, verbose_name='Source'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='usergroup',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='last_name',
|
||||
field=models.CharField(blank=True, max_length=150, verbose_name='last name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='usergroup',
|
||||
name='name',
|
||||
field=models.CharField(max_length=128, verbose_name='Name'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='usergroup',
|
||||
unique_together={('org_id', 'name')},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='usergroup',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='date_password_last_updated',
|
||||
field=models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date password last updated'),
|
||||
),
|
||||
]
|
|
@ -12,19 +12,4 @@ class Migration(migrations.Migration):
|
|||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='loginlog',
|
||||
name='mfa',
|
||||
field=models.SmallIntegerField(choices=[(0, 'Disabled'), (1, 'Enabled'), (2, '-')], default=2, verbose_name='MFA'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='loginlog',
|
||||
name='reason',
|
||||
field=models.SmallIntegerField(choices=[(0, '-'), (1, 'Username/password check failed'), (2, 'MFA authentication failed')], default=0, verbose_name='Reason'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='loginlog',
|
||||
name='status',
|
||||
field=models.BooleanField(choices=[(True, 'Success'), (False, 'Failed')], default=True, max_length=2, verbose_name='Status'),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -10,14 +10,4 @@ class Migration(migrations.Migration):
|
|||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='loginlog',
|
||||
name='reason',
|
||||
field=models.SmallIntegerField(choices=[(0, '-'), (1, 'Username/password check failed'), (2, 'MFA authentication failed'), (3, 'Username does not exist')], default=0, verbose_name='Reason'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='loginlog',
|
||||
name='username',
|
||||
field=models.CharField(max_length=128, verbose_name='Username'),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
# Generated by Django 2.1.1 on 2018-11-23 03:13
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -17,14 +15,4 @@ class Migration(migrations.Migration):
|
|||
name='date_password_last_updated',
|
||||
field=models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date password last updated'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='accesskey',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='access_keys', to=settings.AUTH_USER_MODEL, verbose_name='User'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='loginlog',
|
||||
name='reason',
|
||||
field=models.SmallIntegerField(choices=[(0, '-'), (1, 'Username/password check failed'), (2, 'MFA authentication failed'), (3, 'Username does not exist'), (4, 'Password expired')], default=0, verbose_name='Reason'),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -4,5 +4,4 @@
|
|||
|
||||
from .user import *
|
||||
from .group import *
|
||||
from .authentication import *
|
||||
from .utils import *
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import uuid
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework.authtoken.models import Token
|
||||
from .user import User
|
||||
|
||||
__all__ = ['AccessKey', 'PrivateToken', 'LoginLog']
|
||||
|
||||
|
||||
class AccessKey(models.Model):
|
||||
id = models.UUIDField(verbose_name='AccessKeyID', primary_key=True,
|
||||
default=uuid.uuid4, editable=False)
|
||||
secret = models.UUIDField(verbose_name='AccessKeySecret',
|
||||
default=uuid.uuid4, editable=False)
|
||||
user = models.ForeignKey(User, verbose_name='User',
|
||||
on_delete=models.CASCADE, related_name='access_keys')
|
||||
|
||||
def get_id(self):
|
||||
return str(self.id)
|
||||
|
||||
def get_secret(self):
|
||||
return str(self.secret)
|
||||
|
||||
def get_full_value(self):
|
||||
return '{}:{}'.format(self.id, self.secret)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.id)
|
||||
|
||||
|
||||
class PrivateToken(Token):
|
||||
"""Inherit from auth token, otherwise migration is boring"""
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Private Token')
|
||||
|
||||
|
||||
class LoginLog(models.Model):
|
||||
LOGIN_TYPE_CHOICE = (
|
||||
('W', 'Web'),
|
||||
('T', 'Terminal'),
|
||||
)
|
||||
|
||||
MFA_DISABLED = 0
|
||||
MFA_ENABLED = 1
|
||||
MFA_UNKNOWN = 2
|
||||
|
||||
MFA_CHOICE = (
|
||||
(MFA_DISABLED, _('Disabled')),
|
||||
(MFA_ENABLED, _('Enabled')),
|
||||
(MFA_UNKNOWN, _('-')),
|
||||
)
|
||||
|
||||
REASON_NOTHING = 0
|
||||
REASON_PASSWORD = 1
|
||||
REASON_MFA = 2
|
||||
REASON_NOT_EXIST = 3
|
||||
REASON_PASSWORD_EXPIRED = 4
|
||||
|
||||
REASON_CHOICE = (
|
||||
(REASON_NOTHING, _('-')),
|
||||
(REASON_PASSWORD, _('Username/password check failed')),
|
||||
(REASON_MFA, _('MFA authentication failed')),
|
||||
(REASON_NOT_EXIST, _("Username does not exist")),
|
||||
(REASON_PASSWORD_EXPIRED, _("Password expired")),
|
||||
)
|
||||
|
||||
STATUS_CHOICE = (
|
||||
(True, _('Success')),
|
||||
(False, _('Failed'))
|
||||
)
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
username = models.CharField(max_length=128, verbose_name=_('Username'))
|
||||
type = models.CharField(choices=LOGIN_TYPE_CHOICE, max_length=2, verbose_name=_('Login type'))
|
||||
ip = models.GenericIPAddressField(verbose_name=_('Login ip'))
|
||||
city = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('Login city'))
|
||||
user_agent = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('User agent'))
|
||||
mfa = models.SmallIntegerField(default=MFA_UNKNOWN, choices=MFA_CHOICE, verbose_name=_('MFA'))
|
||||
reason = models.SmallIntegerField(default=REASON_NOTHING, choices=REASON_CHOICE, verbose_name=_('Reason'))
|
||||
status = models.BooleanField(max_length=2, default=True, choices=STATUS_CHOICE, verbose_name=_('Status'))
|
||||
datetime = models.DateTimeField(auto_now_add=True, verbose_name=_('Date login'))
|
||||
|
||||
class Meta:
|
||||
ordering = ['-datetime', 'username']
|
|
@ -16,7 +16,6 @@ from django.utils import timezone
|
|||
from django.shortcuts import reverse
|
||||
|
||||
from common.utils import get_signer, date_expired_default
|
||||
from orgs.utils import current_org
|
||||
|
||||
|
||||
__all__ = ['User']
|
||||
|
@ -104,6 +103,8 @@ class User(AbstractUser):
|
|||
verbose_name=_('Date password last updated')
|
||||
)
|
||||
|
||||
user_cache_key_prefix = '_User_{}'
|
||||
|
||||
def __str__(self):
|
||||
return '{0.name}({0.username})'.format(self)
|
||||
|
||||
|
@ -281,6 +282,7 @@ class User(AbstractUser):
|
|||
self.role = 'Admin'
|
||||
self.is_active = True
|
||||
super().save(*args, **kwargs)
|
||||
self.expire_user_cache()
|
||||
|
||||
@property
|
||||
def private_token(self):
|
||||
|
@ -422,8 +424,26 @@ class User(AbstractUser):
|
|||
def delete(self, using=None, keep_parents=False):
|
||||
if self.pk == 1 or self.username == 'admin':
|
||||
return
|
||||
self.expire_user_cache()
|
||||
return super(User, self).delete()
|
||||
|
||||
def expire_user_cache(self):
|
||||
key = self.user_cache_key_prefix.format(self.id)
|
||||
cache.delete(key)
|
||||
|
||||
@classmethod
|
||||
def get_user_or_from_cache(cls, uid):
|
||||
key = cls.user_cache_key_prefix.format(uid)
|
||||
user = cache.get(key)
|
||||
if user:
|
||||
return user
|
||||
try:
|
||||
user = cls.objects.get(id=uid)
|
||||
cache.set(key, user, 3600)
|
||||
except cls.DoesNotExist:
|
||||
user = None
|
||||
return user
|
||||
|
||||
class Meta:
|
||||
ordering = ['username']
|
||||
verbose_name = _("User")
|
||||
|
|
|
@ -2,15 +2,9 @@
|
|||
#
|
||||
from django.utils.translation import ugettext as _
|
||||
from rest_framework import serializers
|
||||
from ..models import User, AccessKey
|
||||
from ..models import User
|
||||
|
||||
|
||||
class AccessKeySerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = AccessKey
|
||||
fields = ['id', 'secret']
|
||||
read_only_fields = ['id', 'secret']
|
||||
from authentication.serializers import AccessKeySerializer
|
||||
|
||||
|
||||
class ServiceAccountSerializer(serializers.ModelSerializer):
|
||||
|
|
|
@ -2,4 +2,3 @@ from django.dispatch import Signal
|
|||
|
||||
|
||||
post_user_create = Signal(providing_args=('user',))
|
||||
|
||||
|
|
|
@ -9,18 +9,13 @@ from celery import shared_task
|
|||
from ops.celery.utils import create_or_update_celery_periodic_tasks
|
||||
from ops.celery.decorator import after_app_ready_start, register_as_period_task
|
||||
from common.utils import get_logger
|
||||
from .models import User, LoginLog
|
||||
from .utils import write_login_log, send_password_expiration_reminder_mail
|
||||
from .models import User
|
||||
from .utils import send_password_expiration_reminder_mail
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
@shared_task
|
||||
def write_login_log_async(*args, **kwargs):
|
||||
write_login_log(*args, **kwargs)
|
||||
|
||||
|
||||
@shared_task
|
||||
def check_password_expired():
|
||||
users = User.objects.exclude(role=User.ROLE_APP)
|
||||
|
@ -48,13 +43,4 @@ def check_password_expired_periodic():
|
|||
create_or_update_celery_periodic_tasks(tasks)
|
||||
|
||||
|
||||
@register_as_period_task(interval=3600*24)
|
||||
@shared_task
|
||||
def clean_login_log_period():
|
||||
now = timezone.now()
|
||||
try:
|
||||
days = int(settings.LOGIN_LOG_KEEP_DAYS)
|
||||
except ValueError:
|
||||
days = 90
|
||||
expired_day = now - datetime.timedelta(days=days)
|
||||
LoginLog.objects.filter(datetime__lt=expired_day).delete()
|
||||
|
||||
|
|
|
@ -88,7 +88,7 @@
|
|||
<div class="hr-line-dashed"></div>
|
||||
<p class="text-muted text-center">{% trans "More login options" %}</p>
|
||||
<div>
|
||||
<button type="button" class="btn btn-default btn-sm btn-block" onclick="location.href='{% url 'authentication:openid-login' %}'">
|
||||
<button type="button" class="btn btn-default btn-sm btn-block" onclick="location.href='{% url 'authentication:openid:openid-login' %}'">
|
||||
<i class="fa fa-openid"></i>
|
||||
{% trans 'Keycloak' %}
|
||||
</button>
|
||||
|
|
|
@ -5,6 +5,8 @@ from __future__ import absolute_import
|
|||
|
||||
from django.urls import path
|
||||
from rest_framework_bulk.routes import BulkRouter
|
||||
|
||||
from authentication import api as auth_api
|
||||
from .. import api
|
||||
|
||||
app_name = 'users'
|
||||
|
@ -15,11 +17,12 @@ router.register(r'groups', api.UserGroupViewSet, 'user-group')
|
|||
|
||||
|
||||
urlpatterns = [
|
||||
# path('token/', api.UserToken.as_view(), name='user-token'),
|
||||
path('connection-token/', api.UserConnectionTokenApi.as_view(), name='connection-token'),
|
||||
path('connection-token/', auth_api.UserConnectionTokenApi.as_view(),
|
||||
name='connection-token'),
|
||||
path('auth/', auth_api.UserAuthApi.as_view(), name='user-auth'),
|
||||
path('otp/auth/', auth_api.UserOtpAuthApi.as_view(), name='user-otp-auth'),
|
||||
|
||||
path('profile/', api.UserProfileApi.as_view(), name='user-profile'),
|
||||
path('auth/', api.UserAuthApi.as_view(), name='user-auth'),
|
||||
path('otp/auth/', api.UserOtpAuthApi.as_view(), name='user-otp-auth'),
|
||||
path('otp/reset/', api.UserResetOTPApi.as_view(), name='my-otp-reset'),
|
||||
path('users/<uuid:pk>/otp/reset/', api.UserResetOTPApi.as_view(), name='user-reset-otp'),
|
||||
path('users/<uuid:pk>/password/', api.UserChangePasswordApi.as_view(), name='change-user-password'),
|
||||
|
|
|
@ -9,8 +9,6 @@ app_name = 'users'
|
|||
urlpatterns = [
|
||||
# Login view
|
||||
path('login/', views.UserLoginView.as_view(), name='login'),
|
||||
path('logout/', views.UserLogoutView.as_view(), name='logout'),
|
||||
path('login/otp/', views.UserLoginOtpView.as_view(), name='login-otp'),
|
||||
path('password/forgot/', views.UserForgotPasswordView.as_view(), name='forgot-password'),
|
||||
path('password/forgot/sendmail-success/', views.UserForgotPasswordSendmailSuccessView.as_view(), name='forgot-password-sendmail-success'),
|
||||
path('password/reset/', views.UserResetPasswordView.as_view(), name='reset-password'),
|
||||
|
@ -48,7 +46,4 @@ urlpatterns = [
|
|||
path('user-group/<uuid:pk>/update/', views.UserGroupUpdateView.as_view(), name='user-group-update'),
|
||||
path('user-group/<uuid:pk>/assets/', views.UserGroupGrantedAssetView.as_view(), name='user-group-granted-asset'),
|
||||
|
||||
# Login log
|
||||
# Abandon
|
||||
path('login-log/', views.LoginLogListView.as_view(), name='login-log-list'),
|
||||
]
|
||||
|
|
|
@ -7,7 +7,6 @@ import pyotp
|
|||
import base64
|
||||
import logging
|
||||
|
||||
import requests
|
||||
import ipaddress
|
||||
from django.http import Http404
|
||||
from django.conf import settings
|
||||
|
@ -18,8 +17,8 @@ from django.core.cache import cache
|
|||
from datetime import datetime
|
||||
|
||||
from common.tasks import send_mail_async
|
||||
from common.utils import reverse, get_object_or_none
|
||||
from .models import User, LoginLog
|
||||
from common.utils import reverse, get_object_or_none, get_ip_city
|
||||
from .models import User
|
||||
|
||||
|
||||
logger = logging.getLogger('jumpserver')
|
||||
|
@ -63,7 +62,7 @@ def send_user_created_mail(user):
|
|||
'rest_password_token': user.generate_reset_token(),
|
||||
'forget_password_url': reverse('users:forgot-password', external=True),
|
||||
'email': user.email,
|
||||
'login_url': reverse('users:login', external=True),
|
||||
'login_url': reverse('authentication:login', external=True),
|
||||
}
|
||||
if settings.DEBUG:
|
||||
try:
|
||||
|
@ -99,7 +98,7 @@ def send_reset_password_mail(user):
|
|||
'rest_password_token': user.generate_reset_token(),
|
||||
'forget_password_url': reverse('users:forgot-password', external=True),
|
||||
'email': user.email,
|
||||
'login_url': reverse('users:login', external=True),
|
||||
'login_url': reverse('authentication:login', external=True),
|
||||
}
|
||||
if settings.DEBUG:
|
||||
logger.debug(message)
|
||||
|
@ -137,7 +136,7 @@ def send_password_expiration_reminder_mail(user):
|
|||
'update_password_url': reverse('users:user-password-update', external=True),
|
||||
'forget_password_url': reverse('users:forgot-password', external=True),
|
||||
'email': user.email,
|
||||
'login_url': reverse('users:login', external=True),
|
||||
'login_url': reverse('authentication:login', external=True),
|
||||
}
|
||||
if settings.DEBUG:
|
||||
logger.debug(message)
|
||||
|
@ -159,7 +158,7 @@ def send_reset_ssh_key_mail(user):
|
|||
</br>
|
||||
""") % {
|
||||
'name': user.name,
|
||||
'login_url': reverse('users:login', external=True),
|
||||
'login_url': reverse('authentication:login', external=True),
|
||||
}
|
||||
if settings.DEBUG:
|
||||
logger.debug(message)
|
||||
|
@ -199,51 +198,6 @@ def check_user_valid(**kwargs):
|
|||
return None, _('Password or SSH public key invalid')
|
||||
|
||||
|
||||
def validate_ip(ip):
|
||||
try:
|
||||
ipaddress.ip_address(ip)
|
||||
return True
|
||||
except ValueError:
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def write_login_log(*args, **kwargs):
|
||||
ip = kwargs.get('ip', '')
|
||||
if not (ip and validate_ip(ip)):
|
||||
ip = ip[:15]
|
||||
city = "Unknown"
|
||||
else:
|
||||
city = get_ip_city(ip)
|
||||
kwargs.update({'ip': ip, 'city': city})
|
||||
LoginLog.objects.create(**kwargs)
|
||||
|
||||
|
||||
def get_ip_city(ip, timeout=10):
|
||||
# Taobao ip api: http://ip.taobao.com/service/getIpInfo.php?ip=8.8.8.8
|
||||
# Sina ip api: http://int.dpool.sina.com.cn/iplookup/iplookup.php?ip=8.8.8.8&format=json
|
||||
|
||||
url = 'http://ip.taobao.com/service/getIpInfo.php?ip=%s' % ip
|
||||
try:
|
||||
r = requests.get(url, timeout=timeout)
|
||||
except:
|
||||
r = None
|
||||
city = 'Unknown'
|
||||
if r and r.status_code == 200:
|
||||
try:
|
||||
data = r.json()
|
||||
if not isinstance(data, int) and data['code'] == 0:
|
||||
country = data['data']['country']
|
||||
_city = data['data']['city']
|
||||
if country == 'XX':
|
||||
city = _city
|
||||
else:
|
||||
city = ' '.join([country, _city])
|
||||
except ValueError:
|
||||
pass
|
||||
return city
|
||||
|
||||
|
||||
def get_user_or_tmp_user(request):
|
||||
user = request.user
|
||||
tmp_user = get_tmp_user_from_cache(request)
|
||||
|
|
|
@ -1,244 +1,35 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import os
|
||||
from django.core.cache import cache
|
||||
from django.shortcuts import render
|
||||
from django.contrib.auth import login as auth_login, logout as auth_logout
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.views.generic import ListView
|
||||
from django.views.generic import RedirectView
|
||||
from django.core.files.storage import default_storage
|
||||
from django.http import HttpResponseRedirect, HttpResponse
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import reverse, redirect
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.decorators.cache import never_cache
|
||||
from django.views.decorators.csrf import csrf_protect
|
||||
from django.views.decorators.debug import sensitive_post_parameters
|
||||
from django.views.generic.base import TemplateView
|
||||
from django.views.generic.edit import FormView
|
||||
from django.conf import settings
|
||||
from django.urls import reverse_lazy
|
||||
from formtools.wizard.views import SessionWizardView
|
||||
|
||||
from common.utils import get_object_or_none, get_request_ip
|
||||
from ..models import User, LoginLog
|
||||
from ..utils import send_reset_password_mail, check_otp_code, \
|
||||
redirect_user_first_login_or_index, get_user_or_tmp_user, \
|
||||
set_tmp_user_to_cache, get_password_check_rules, check_password_rules, \
|
||||
is_block_login, increase_login_failed_count, clean_failed_count
|
||||
from ..tasks import write_login_log_async
|
||||
from common.utils import get_object_or_none
|
||||
from ..models import User
|
||||
from ..utils import (
|
||||
send_reset_password_mail, get_password_check_rules, check_password_rules
|
||||
)
|
||||
from .. import forms
|
||||
|
||||
|
||||
__all__ = [
|
||||
'UserLoginView', 'UserLoginOtpView', 'UserLogoutView',
|
||||
'UserForgotPasswordView', 'UserForgotPasswordSendmailSuccessView',
|
||||
'UserResetPasswordView', 'UserResetPasswordSuccessView',
|
||||
'UserFirstLoginView', 'LoginLogListView'
|
||||
'UserLoginView', 'UserForgotPasswordSendmailSuccessView',
|
||||
'UserResetPasswordSuccessView', 'UserResetPasswordSuccessView',
|
||||
'UserResetPasswordView', 'UserForgotPasswordView', 'UserFirstLoginView',
|
||||
]
|
||||
|
||||
|
||||
@method_decorator(sensitive_post_parameters(), name='dispatch')
|
||||
@method_decorator(csrf_protect, name='dispatch')
|
||||
@method_decorator(never_cache, name='dispatch')
|
||||
class UserLoginView(FormView):
|
||||
form_class = forms.UserLoginForm
|
||||
form_class_captcha = forms.UserLoginCaptchaForm
|
||||
redirect_field_name = 'next'
|
||||
key_prefix_captcha = "_LOGIN_INVALID_{}"
|
||||
|
||||
def get_template_names(self):
|
||||
template_name = 'users/login.html'
|
||||
if not settings.XPACK_ENABLED:
|
||||
return template_name
|
||||
|
||||
from xpack.plugins.license.models import License
|
||||
if not License.has_valid_license():
|
||||
return template_name
|
||||
|
||||
template_name = 'users/new_login.html'
|
||||
return template_name
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
if request.user.is_staff:
|
||||
return redirect(redirect_user_first_login_or_index(
|
||||
request, self.redirect_field_name)
|
||||
)
|
||||
request.session.set_test_cookie()
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
# limit login authentication
|
||||
ip = get_request_ip(request)
|
||||
username = self.request.POST.get('username')
|
||||
if is_block_login(username, ip):
|
||||
return self.render_to_response(self.get_context_data(block_login=True))
|
||||
return super().post(request, *args, **kwargs)
|
||||
|
||||
def form_valid(self, form):
|
||||
if not self.request.session.test_cookie_worked():
|
||||
return HttpResponse(_("Please enable cookies and try again."))
|
||||
|
||||
user = form.get_user()
|
||||
|
||||
# user password expired
|
||||
if user.password_has_expired:
|
||||
data = {
|
||||
'username': user.username,
|
||||
'mfa': int(user.otp_enabled),
|
||||
'reason': LoginLog.REASON_PASSWORD_EXPIRED,
|
||||
'status': False
|
||||
}
|
||||
self.write_login_log(data)
|
||||
return self.render_to_response(self.get_context_data(password_expired=True))
|
||||
|
||||
set_tmp_user_to_cache(self.request, user)
|
||||
username = form.cleaned_data.get('username')
|
||||
ip = get_request_ip(self.request)
|
||||
# 登陆成功,清除缓存计数
|
||||
clean_failed_count(username, ip)
|
||||
return redirect(self.get_success_url())
|
||||
|
||||
def form_invalid(self, form):
|
||||
# write login failed log
|
||||
username = form.cleaned_data.get('username')
|
||||
exist = User.objects.filter(username=username).first()
|
||||
reason = LoginLog.REASON_PASSWORD if exist else LoginLog.REASON_NOT_EXIST
|
||||
data = {
|
||||
'username': username,
|
||||
'mfa': LoginLog.MFA_UNKNOWN,
|
||||
'reason': reason,
|
||||
'status': False
|
||||
}
|
||||
self.write_login_log(data)
|
||||
|
||||
# limit user login failed count
|
||||
ip = get_request_ip(self.request)
|
||||
increase_login_failed_count(username, ip)
|
||||
|
||||
# show captcha
|
||||
cache.set(self.key_prefix_captcha.format(ip), 1, 3600)
|
||||
old_form = form
|
||||
form = self.form_class_captcha(data=form.data)
|
||||
form._errors = old_form.errors
|
||||
return super().form_invalid(form)
|
||||
|
||||
def get_form_class(self):
|
||||
ip = get_request_ip(self.request)
|
||||
if cache.get(self.key_prefix_captcha.format(ip)):
|
||||
return self.form_class_captcha
|
||||
else:
|
||||
return self.form_class
|
||||
|
||||
def get_success_url(self):
|
||||
user = get_user_or_tmp_user(self.request)
|
||||
|
||||
if user.otp_enabled and user.otp_secret_key:
|
||||
# 1,2,mfa_setting & T
|
||||
return reverse('users:login-otp')
|
||||
elif user.otp_enabled and not user.otp_secret_key:
|
||||
# 1,2,mfa_setting & F
|
||||
return reverse('users:user-otp-enable-authentication')
|
||||
elif not user.otp_enabled:
|
||||
# 0 & T,F
|
||||
auth_login(self.request, user)
|
||||
data = {
|
||||
'username': self.request.user.username,
|
||||
'mfa': int(self.request.user.otp_enabled),
|
||||
'reason': LoginLog.REASON_NOTHING,
|
||||
'status': True
|
||||
}
|
||||
self.write_login_log(data)
|
||||
return redirect_user_first_login_or_index(self.request, self.redirect_field_name)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'demo_mode': os.environ.get("DEMO_MODE"),
|
||||
'AUTH_OPENID': settings.AUTH_OPENID,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def write_login_log(self, data):
|
||||
login_ip = get_request_ip(self.request)
|
||||
user_agent = self.request.META.get('HTTP_USER_AGENT', '')
|
||||
tmp_data = {
|
||||
'ip': login_ip,
|
||||
'type': 'W',
|
||||
'user_agent': user_agent
|
||||
}
|
||||
data.update(tmp_data)
|
||||
write_login_log_async.delay(**data)
|
||||
|
||||
|
||||
class UserLoginOtpView(FormView):
|
||||
template_name = 'users/login_otp.html'
|
||||
form_class = forms.UserCheckOtpCodeForm
|
||||
redirect_field_name = 'next'
|
||||
|
||||
def form_valid(self, form):
|
||||
user = get_user_or_tmp_user(self.request)
|
||||
otp_code = form.cleaned_data.get('otp_code')
|
||||
otp_secret_key = user.otp_secret_key
|
||||
|
||||
if check_otp_code(otp_secret_key, otp_code):
|
||||
auth_login(self.request, user)
|
||||
data = {
|
||||
'username': self.request.user.username,
|
||||
'mfa': int(self.request.user.otp_enabled),
|
||||
'reason': LoginLog.REASON_NOTHING,
|
||||
'status': True
|
||||
}
|
||||
self.write_login_log(data)
|
||||
return redirect(self.get_success_url())
|
||||
else:
|
||||
data = {
|
||||
'username': user.username,
|
||||
'mfa': int(user.otp_enabled),
|
||||
'reason': LoginLog.REASON_MFA,
|
||||
'status': False
|
||||
}
|
||||
self.write_login_log(data)
|
||||
form.add_error('otp_code', _('MFA code invalid, or ntp sync server time'))
|
||||
return super().form_invalid(form)
|
||||
|
||||
def get_success_url(self):
|
||||
return redirect_user_first_login_or_index(self.request, self.redirect_field_name)
|
||||
|
||||
def write_login_log(self, data):
|
||||
login_ip = get_request_ip(self.request)
|
||||
user_agent = self.request.META.get('HTTP_USER_AGENT', '')
|
||||
tmp_data = {
|
||||
'ip': login_ip,
|
||||
'type': 'W',
|
||||
'user_agent': user_agent
|
||||
}
|
||||
data.update(tmp_data)
|
||||
write_login_log_async.delay(**data)
|
||||
|
||||
|
||||
@method_decorator(never_cache, name='dispatch')
|
||||
class UserLogoutView(TemplateView):
|
||||
template_name = 'flash_message_standalone.html'
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
auth_logout(request)
|
||||
next_uri = request.COOKIES.get("next")
|
||||
if next_uri:
|
||||
return redirect(next_uri)
|
||||
response = super().get(request, *args, **kwargs)
|
||||
return response
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'title': _('Logout success'),
|
||||
'messages': _('Logout success, return login page'),
|
||||
'interval': 1,
|
||||
'redirect_url': reverse('users:login'),
|
||||
'auto_redirect': True,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
class UserLoginView(RedirectView):
|
||||
urls = reverse_lazy('authentication:login')
|
||||
|
||||
|
||||
class UserForgotPasswordView(TemplateView):
|
||||
|
@ -267,7 +58,7 @@ class UserForgotPasswordSendmailSuccessView(TemplateView):
|
|||
'title': _('Send reset password message'),
|
||||
'messages': _('Send reset password mail success, '
|
||||
'login your mail box and follow it '),
|
||||
'redirect_url': reverse('users:login'),
|
||||
'redirect_url': reverse('authentication:login'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
@ -280,7 +71,7 @@ class UserResetPasswordSuccessView(TemplateView):
|
|||
context = {
|
||||
'title': _('Reset password success'),
|
||||
'messages': _('Reset password success, return to login page'),
|
||||
'redirect_url': reverse('users:login'),
|
||||
'redirect_url': reverse('authentication:login'),
|
||||
'auto_redirect': True,
|
||||
}
|
||||
kwargs.update(context)
|
||||
|
@ -386,8 +177,3 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView):
|
|||
form.fields["otp_level"].initial = self.request.user.otp_level
|
||||
|
||||
return form
|
||||
|
||||
|
||||
class LoginLogListView(ListView):
|
||||
def get(self, request, *args, **kwargs):
|
||||
return redirect(reverse('audits:login-log-list'))
|
||||
|
|
|
@ -574,7 +574,7 @@ class UserOtpSettingsSuccessView(TemplateView):
|
|||
'title': title,
|
||||
'messages': describe,
|
||||
'interval': 1,
|
||||
'redirect_url': reverse('users:login'),
|
||||
'redirect_url': reverse('authentication:login'),
|
||||
'auto_redirect': True,
|
||||
}
|
||||
kwargs.update(context)
|
||||
|
|
|
@ -17,7 +17,7 @@ decorator==4.1.2
|
|||
Django==2.1.7
|
||||
django-auth-ldap==1.7.0
|
||||
django-bootstrap3==9.1.0
|
||||
django-celery-beat==1.1.1
|
||||
django-celery-beat==1.4.0
|
||||
django-filter==2.0.0
|
||||
django-formtools==2.1
|
||||
django-ranged-response==0.2.0
|
||||
|
@ -79,3 +79,4 @@ rest_condition==1.0.3
|
|||
python-ldap==3.1.0
|
||||
tencentcloud-sdk-python==3.0.40
|
||||
django-radius==1.3.3
|
||||
ipip-ipdb==1.2.1
|
||||
|
|
Loading…
Reference in New Issue