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