Bulk of the profile work is done.

This commit is contained in:
Adarnof
2017-03-23 22:54:25 -04:00
parent bb87fdd958
commit e15d79b834
155 changed files with 1693 additions and 3080 deletions

View File

@@ -5,39 +5,11 @@ from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User
from django.utils.text import slugify
from django import forms
from authentication.models import AuthServicesInfo, State, get_none_state
from eveonline.models import EveCharacter
from authentication.models import State, get_guest_state
from alliance_auth.hooks import get_hooks
from services.hooks import ServicesHook
@admin.register(AuthServicesInfo)
class AuthServicesInfoManager(admin.ModelAdmin):
@staticmethod
def main_character(obj):
if obj.main_char_id:
try:
return EveCharacter.objects.get(character_id=obj.main_char_id)
except EveCharacter.DoesNotExist:
pass
return None
@staticmethod
def has_delete_permission(request, obj=None):
return False
@staticmethod
def has_add_permission(request, obj=None):
return False
search_fields = [
'user__username',
'main_char_id',
]
list_display = ('user', 'main_character')
def make_service_hooks_update_groups_action(service):
"""
Make a admin action for the given service
@@ -103,16 +75,16 @@ class StateForm(forms.ModelForm):
def _is_none_state(self):
instance = getattr(self, 'instance', None)
if instance and instance.pk:
return instance == get_none_state()
return instance == get_guest_state()
def __init__(self, *args, **kwargs):
super(StateForm, self).__init__(*args, **kwargs)
if _is_none_state():
if self._is_none_state():
self.fields['name'].widget.attrs['readonly'] = True
def clean_name(self):
if self._is_none_state():
return instance.name
return self.instance.name
return self.cleaned_data['name']
@@ -122,6 +94,6 @@ class StateAdmin(admin.ModelAdmin):
@staticmethod
def has_delete_permission(request, obj=None):
if obj == get_none_state():
if obj == get_guest_state():
return False

View File

@@ -1,10 +1,14 @@
from __future__ import unicode_literals
from django.apps import AppConfig
from django.core.checks import register, Tags
class AuthenticationConfig(AppConfig):
name = 'authentication'
def ready(self):
super(AuthenticationConfig, self).ready()
import authentication.signals
from authentication import checks
register(Tags.security)(checks.check_login_scopes_setting)

View File

@@ -1,6 +1,7 @@
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import Permission
from authentication.models import UserProfile
from authentication.models import UserProfile, CharacterOwnership
from django.contrib.auth.models import User
class StateBackend(ModelBackend):
@@ -10,7 +11,7 @@ class StateBackend(ModelBackend):
user_state_query = 'state__%s__user' % profile_state_field.related_query_name()
return Permission.objects.filter(**{user_state_query: user_obj})
def get_state_permission(self, user_obj, obj=None):
def get_state_permissions(self, user_obj, obj=None):
return self._get_permissions(user_obj, obj, 'state')
def get_all_permissions(self, user_obj, obj=None):
@@ -21,3 +22,49 @@ class StateBackend(ModelBackend):
user_obj._perm_cache.update(self.get_group_permissions(user_obj))
user_obj._perm_cache.update(self.get_state_permissions(user_obj))
return user_obj._perm_cache
def authenticate(self, token=None):
if not token:
return None
try:
ownership = CharacterOwnership.objects.get(character__character_id=token.character_id)
if ownership.owner_hash == token.character_owner_hash:
return ownership.user
else:
ownership.delete()
return self.create_user(token)
except CharacterOwnership.DoesNotExist:
try:
# insecure legacy main check for pre-sso registration auth installs
profile = UserProfile.objects.get(main_character__character_id=token.character_id)
# attach an ownership
CharacterOwnership.objects.create_by_token(token)
return profile.user
except UserProfile.DoesNotExist:
pass
return self.create_user(token)
def create_user(self, token):
username = self.iterate_username(token.charater_name) # build unique username off character name
user = User.objects.create_user(username)
user.set_unusable_password() # prevent login via password
user.is_active = False # prevent login until email set
user.save()
token.user = user
co = CharacterOwnership.objects.create_by_token(token) # assign ownership to this user
user.profile.main_character = co.character # assign main character as token character
user.profile.save()
return user
@staticmethod
def iterate_username(name):
if User.objects.filter(username__startswith=name).exists():
u = User.objects.filter(username__startswith=name)
num = len(u)
username = "%s_%s" % (name, num)
while u.filter(username=username).exists():
num += 1
username = "%s_%s" % (name, num)
else:
username = name
return username

13
authentication/checks.py Normal file
View File

@@ -0,0 +1,13 @@
from __future__ import unicode_literals
from django.core.checks import Error
from django.conf import settings
def check_login_scopes_setting(*args, **kwargs):
errors = []
try:
assert settings.LOGIN_TOKEN_SCOPES
except (AssertionError, AttributeError):
errors.append(Error('LOGIN_TOKEN_SCOPES setting cannot be blank.',
hint='SSO tokens used for logging in must require scopes to be refreshable.'))
return errors

View File

@@ -1,17 +0,0 @@
from __future__ import unicode_literals
from authentication.states import NONE_STATE, BLUE_STATE, MEMBER_STATE
from authentication.managers import UserState
from django.conf import settings
def membership_state(request):
return UserState.get_membership_state(request)
def states(request):
return {
'BLUE_STATE': BLUE_STATE,
'MEMBER_STATE': MEMBER_STATE,
'NONE_STATE': NONE_STATE,
'MEMBER_BLUE_STATE': [MEMBER_STATE, BLUE_STATE],
}

View File

@@ -1,23 +0,0 @@
from __future__ import unicode_literals
from django.contrib.auth.decorators import user_passes_test
from authentication.managers import UserState
def _state_required(state_test, *args, **kwargs):
return user_passes_test(state_test, *args, **kwargs)
def members(*args, **kwargs):
return _state_required(UserState.member_state, *args, **kwargs)
def blues(*args, **kwargs):
return _state_required(UserState.blue_state, *args, **kwargs)
def members_and_blues(*args, **kwargs):
return _state_required(UserState.member_or_blue_state, *args, **kwargs)
def none_state(*args, **kwargs):
return _state_required(UserState.none_state, *args, **kwargs)

View File

@@ -1,42 +1,7 @@
from __future__ import unicode_literals
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
import re
class LoginForm(forms.Form):
username = forms.CharField(label=_('Username'), max_length=32, required=True)
password = forms.CharField(label=_('Password'), widget=forms.PasswordInput())
class RegistrationForm(forms.Form):
username = forms.CharField(label=_('Username'), max_length=30, required=True)
password = forms.CharField(label=_('Password'), widget=forms.PasswordInput(), required=True)
password_again = forms.CharField(label=_('Password Again'), widget=forms.PasswordInput(), required=True)
email = forms.CharField(label=_('Email'), max_length=254, required=True)
email_again = forms.CharField(label=_('Email Again'), max_length=254, required=True)
def clean(self):
if ' ' in self.cleaned_data['username']:
raise forms.ValidationError('Username cannot contain a space')
# We attempt to get the user object if we succeed we know email as been used
try:
User.objects.get(email=self.cleaned_data['email'])
raise forms.ValidationError('Email as already been used')
except User.DoesNotExist:
pass
if not re.match("^\w+$", self.cleaned_data['username']):
raise forms.ValidationError('Username contains illegal characters')
if 'password' in self.cleaned_data and 'password_again' in self.cleaned_data:
if self.cleaned_data['password'] != self.cleaned_data['password_again']:
raise forms.ValidationError('Passwords do not match')
if 'email' in self.cleaned_data and 'email_again' in self.cleaned_data:
if self.cleaned_data['email'] != self.cleaned_data['email_again']:
raise forms.ValidationError('Emails do not match')
return self.cleaned_data
email = forms.EmailField(label=_('Email'), max_length=254, required=True)

View File

@@ -1,75 +1,57 @@
from __future__ import unicode_literals
from django.contrib.auth.models import User
from django.conf import settings
from authentication.states import NONE_STATE, BLUE_STATE, MEMBER_STATE
from authentication.models import AuthServicesInfo
from django.db.models import Manager, QuerySet, Q
from eveonline.managers import EveManager
from eveonline.models import EveCharacter
import logging
logger = logging.getLogger(__name__)
class AuthServicesInfoManager:
def __init__(self):
pass
class CharacterOwnershipManager(Manager):
def create_by_token(self, token):
if not EveCharacter.objects.filter(character_id=token.character_id).exists():
EveManager.create_character(token.character_id)
return self.create(character=EveCharacter.objects.get(characte_id=token.character_id), user=token.user,
owner_hash=token.character_owner_hash)
@staticmethod
def update_main_char_id(char_id, user):
if User.objects.filter(username=user.username).exists():
logger.debug("Updating user %s main character to id %s" % (user, char_id))
authserviceinfo = AuthServicesInfo.objects.get(user=user)
authserviceinfo.main_char_id = char_id
authserviceinfo.save(update_fields=['main_char_id'])
logger.info("Updated user %s main character to id %s" % (user, char_id))
class StateQuerySet(QuerySet):
def available_to_character(self, character):
query = Q(member_characters__character_id=character.character_id)
query |= Q(member_corporations__corporation_id=character.corporation_id)
query |= Q(member_alliances__alliance_id=character.alliance_id)
query |= Q(public=True)
return self.filter(query)
def available_to_user(self, user):
if user.profile.main_character:
return self.available_to_character(user.profile.main_character)
else:
logger.error("Failed to update user %s main character id to %s: user does not exist." % (user, char_id))
@staticmethod
def update_is_blue(is_blue, user):
if User.objects.filter(username=user.username).exists():
logger.debug("Updating user %s blue status: %s" % (user, is_blue))
authserviceinfo = AuthServicesInfo.objects.get(user=user)
authserviceinfo.is_blue = is_blue
authserviceinfo.save(update_fields=['is_blue'])
logger.info("Updated user %s blue status to %s in authservicesinfo model." % (user, is_blue))
return self.none()
class UserState:
def __init__(self):
pass
class StateManager(Manager):
def get_queryset(self):
return StateQuerySet(self.model, using=self._db)
MEMBER_STATE = MEMBER_STATE
BLUE_STATE = BLUE_STATE
NONE_STATE = NONE_STATE
def available_to_character(self, character):
return self.get_queryset().available_to_character(character)
@classmethod
def member_state(cls, user):
return cls.state_required(user, [cls.MEMBER_STATE])
def available_to_user(self, user):
return self.get_queryset().available_to_user(user)
@classmethod
def member_or_blue_state(cls, user):
return cls.state_required(user, [cls.MEMBER_STATE, cls.BLUE_STATE])
def get_for_character(self, character):
states = self.get_queryset().available_to_character(character).order_by('priority')
if states.exists():
return states[0]
else:
from authentication.models import get_guest_state
return get_guest_state()
@classmethod
def blue_state(cls, user):
return cls.state_required(user, [cls.BLUE_STATE])
@classmethod
def none_state(cls, user):
return cls.state_required(user, [cls.NONE_STATE])
@classmethod
def get_membership_state(cls, request):
if request.user.is_authenticated:
auth = AuthServicesInfo.objects.get(user=request.user)
return {'STATE': auth.state}
return {'STATE': cls.NONE_STATE}
@staticmethod
def state_required(user, states):
if user.is_superuser and settings.SUPERUSER_STATE_BYPASS:
return True
if user.is_authenticated:
auth = AuthServicesInfo.objects.get(user=user)
return auth.state in states
return False
def get_for_user(self, user):
states = self.get_queryset().available_to_user(user)
if states.exists():
return states[0]
else:
from authentication.models import get_guest_state
return get_guest_state()

View File

@@ -4,52 +4,6 @@ from __future__ import unicode_literals
from django.db import migrations
from authentication.states import MEMBER_STATE, BLUE_STATE, NONE_STATE
from django.conf import settings
def determine_membership_by_character(char, apps):
if str(char.corporation_id) in settings.STR_CORP_IDS:
return MEMBER_STATE
elif str(char.alliance_id) in settings.STR_ALLIANCE_IDS:
return MEMBER_STATE
EveCorporationInfo = apps.get_model('eveonline', 'EveCorporationInfo')
if EveCorporationInfo.objects.filter(corporation_id=char.corporation_id).exists() is False:
return NONE_STATE
else:
corp = EveCorporationInfo.objects.get(corporation_id=char.corporation_id)
if corp.is_blue:
return BLUE_STATE
else:
return NONE_STATE
def determine_membership_by_user(user, apps):
AuthServicesInfo = apps.get_model('authentication', 'AuthServicesInfo')
auth = AuthServicesInfo.objects.get(user=user)
if auth.main_char_id:
EveCharacter = apps.get_model('eveonline', 'EveCharacter')
if EveCharacter.objects.filter(character_id=auth.main_char_id).exists():
char = EveCharacter.objects.get(character_id=auth.main_char_id)
return determine_membership_by_character(char, apps)
else:
return NONE_STATE
else:
return NONE_STATE
def set_state(user, apps):
if user.is_active:
state = determine_membership_by_user(user, apps)
else:
state = NONE_STATE
AuthServicesInfo = apps.get_model('authentication', 'AuthServicesInfo')
auth = AuthServicesInfo.objects.get(user=user)
if auth.state != state:
auth.state = state
auth.save()
def set_initial_state(apps, schema_editor):
User = apps.get_model('auth', 'User')
for u in User.objects.all():
set_state(u, apps)
class Migration(migrations.Migration):
@@ -60,5 +14,4 @@ class Migration(migrations.Migration):
]
operations = [
migrations.RunPython(set_initial_state, migrations.RunPython.noop)
]

View File

@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-09 23:19
from __future__ import unicode_literals
from django.db import migrations
def create_permission(apps, schema_editor):
User = apps.get_model('auth', 'User')
ContentType = apps.get_model('contenttypes', 'ContentType')
Permission = apps.get_model('auth', 'Permission')
ct = ContentType.objects.get_for_model(User)
Permission.objects.get_or_create(codename="view_fleetup", content_type=ct, name="view_fleetup")
class Migration(migrations.Migration):
dependencies = [
('authentication', '0013_service_modules'),
]
operations = [
migrations.RunPython(create_permission, migrations.RunPython.noop)
]

View File

@@ -0,0 +1,227 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-03-22 23:09
from __future__ import unicode_literals
import authentication.models
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
def create_guest_state(apps, schema_editor):
State = apps.get_model('authentication', 'State')
State.objects.update_or_create(name='Guest', defaults={'priority': 0, 'public': True})
def create_member_state(apps, schema_editor):
Group = apps.get_model('auth', 'Group')
State = apps.get_model('authentication', 'State')
EveAllianceInfo = apps.get_model('eveonline', 'EveAllianceInfo')
EveCorporationInfo = apps.get_model('eveonline', 'EveCorporationInfo')
member_state_name = getattr(settings, 'DEFAULT_AUTH_GROUP', 'Member')
s = State.objects.update_or_create(name=member_state_name, defaults={'priority': 100, 'public': False})[0]
try:
# move group permissions to state
g = Group.objects.get(name=member_state_name)
s.permissions.add(g.permissions.all())
g.delete()
except Group.DoesNotExist:
pass
# auto-populate member IDs
CORP_IDS = getattr(settings, 'CORP_IDS', [])
ALLIANCE_IDS = getattr(settings, 'ALLIANCE_IDS', [])
s.member_corporations.add(EveCorporationInfo.objects.filter(corporation_id__in=CORP_IDS))
s.member_alliances.add(EveAllianceInfo.objects.filter(alliance_id__in=ALLIANCE_IDS))
def create_member_group(apps, schema_editor):
Group = apps.get_model('auth', 'Group')
State = apps.get_model('authentication', 'State')
member_state_name = getattr(settings, 'DEFAULT_AUTH_GROUP', 'Member')
g = Group.objects.get_or_create(name=member_state_name)[0]
try:
# move permissions back
state = State.objects.get(name=member_state_name)
g.permissions.add(state.permissions.all())
# move users back
for profile in state.userprofile_set.all().select_related('user'):
profile.user.groups.add(g)
except State.DoesNotExist:
pass
def create_blue_state(apps, schema_editor):
Group = apps.get_model('auth', 'Group')
State = apps.get_model('authentication', 'State')
EveAllianceInfo = apps.get_model('eveonline', 'EveAllianceInfo')
EveCorporationInfo = apps.get_model('eveonline', 'EveCorporationInfo')
blue_state_name = getattr(settings, 'DEFAULT_BLUE_GROUP', 'Blue')
s = State.objects.update_or_create(name=blue_state_name, defaults={'priority': 50, 'public': False})[0]
try:
# move group permissions to state
g = Group.objects.get(name=blue_state_name)
s.permissions.add(g.permissions.all())
g.permissions.clear()
except Group.DoesNotExist:
pass
# auto-populate blue member IDs
BLUE_CORP_IDS = getattr(settings, 'BLUE_CORP_IDS', [])
BLUE_ALLIANCE_IDS = getattr(settings, 'BLUE_ALLIANCE_IDS', [])
s.member_corporations.add(EveCorporationInfo.objects.filter(corporation_id__in=BLUE_CORP_IDS))
s.member_alliances.add(EveAllianceInfo.objects.filter(alliance_id__in=BLUE_ALLIANCE_IDS))
def create_blue_group(apps, schema_editor):
Group = apps.get_model('auth', 'Group')
State = apps.get_model('authentication', 'State')
blue_state_name = getattr(settings, 'DEFAULT_BLUE_GROUP', 'Blue')
g = Group.objects.get_or_create(name=blue_state_name)[0]
try:
# move permissions back
state = State.objects.get(name=blue_state_name)
g.permissions.add(state.permissions.all())
# move users back
for profile in state.userprofile_set.all().select_related('user'):
profile.user.groups.add(g)
except State.DoesNotExist:
pass
def populate_ownerships(apps, schema_editor):
Token = apps.get_model('esi', 'Token')
CharacterOwnership = apps.get_model('authentication', 'CharacterOwnership')
EveCharacter = apps.get_model('eveonline', 'EveCharacter')
unique_character_owners = [t['character_id'] for t in
Token.objects.all().values('character_id').annotate(n=models.Count('user')) if
t['n'] == 1 and EveCharacter.objects.filter(character_id=t['character_id'].exists())]
tokens = Token.objects.filter(character_id__in=unique_character_owners)
for c_id in unique_character_owners:
ts = tokens.filter(character_id=c_id).order_by('created')
for t in ts:
if t.can_refresh:
# find newest refreshable token and use it as basis for CharacterOwnership
CharacterOwnership.objecs.create_by_token(t)
break
def create_profiles(apps, schema_editor):
AuthServicesInfo = apps.get_model('authentication', 'AuthServicesInfo')
State = apps.get_model('authentication', 'State')
UserProfile = apps.get_model('authentication', 'UserProfile')
EveCharacter = apps.get_model('eveonline', 'EveCharacter')
# grab AuthServicesInfo if they have a unique main_char_id and the EveCharacter exists
unique_mains = [auth['main_char_id'] for auth in
AuthServicesInfo.objects.exclude(main_char_id='').values('main_char_id').annotate(
n=models.Count('main_char_id')) if
auth['n'] == 1 and EveCharacter.objects.filter(character_id=auth['main_char_id'].exists())]
auths = AuthServicesInfo.objects.filter(main_char_id__in=unique_mains).select_related('user')
for auth in auths:
# carry states and mains forward
profile = UserProfile.objects.get_or_create(user=auth.user.pk)
state = State.objects.get(name=auth.state if auth.state else 'Guest')
profile.state = state
char = EveCharacter.objects.get(character_id=auth.main_char_id)
profile.main_character = char
profile.save()
def recreate_authservicesinfo(apps, schema_editor):
AuthServicesInfo = apps.get_model('authentication', 'AuthServicesInfo')
UserProfile = apps.get_model('authentication', 'UserProfile')
User = apps.get_model('auth', 'User')
# recreate all missing AuthServicesInfo models
AuthServicesInfo.objects.bulk_create([AuthServicesInfo(user=u.pk) for u in User.objects.all()])
# repopulate main characters
for profile in UserProfile.objects.exclude(main_character__isnull=True).select_related('user', 'main_character'):
AuthServicesInfo.objects.update_or_create(user=profile.user, defaults={'main_char_id': profile.main_character.character_id})
# repopulate states we understand
for profile in UserProfile.objects.exclude(state__name='Guest').filter(state__name__in=['Member', 'Blue']).select_related('user', 'state'):
AuthServicesInfo.objects.update_or_create(user=profile.user, defaults={'state': profile.state.name})
class Migration(migrations.Migration):
dependencies = [
('auth', '0008_alter_user_username_max_length'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('eveonline', '0008_remove_apikeys'),
('authentication', '0014_fleetup_permission'),
('esi', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='CharacterOwnership',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('owner_hash', models.CharField(max_length=28, unique=True)),
('character',
models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='character_ownership',
to='eveonline.EveCharacter')),
('user',
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='character_ownerships',
to=settings.AUTH_USER_MODEL)),
],
options={
'default_permissions': ('change', 'delete'),
},
),
migrations.CreateModel(
name='State',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=20, unique=True)),
('priority', models.IntegerField(unique=True)),
('public', models.BooleanField(default=False)),
('member_alliances', models.ManyToManyField(to='eveonline.EveAllianceInfo')),
('member_characters', models.ManyToManyField(to='eveonline.EveCharacter')),
('member_corporations', models.ManyToManyField(to='eveonline.EveCorporationInfo')),
('permissions', models.ManyToManyField(blank=True, to='auth.Permission')),
],
options={
'ordering': ['priority'],
},
),
migrations.CreateModel(
name='UserProfile',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('main_character',
models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL,
to='eveonline.EveCharacter')),
('state', models.ForeignKey(on_delete=models.SET(authentication.models.get_guest_state),
to='authentication.State')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile',
to=settings.AUTH_USER_MODEL)),
],
options={
'default_permissions': ('change',),
},
),
migrations.RunPython(create_guest_state, migrations.RunPython.noop),
migrations.RunPython(create_member_state, create_member_group),
migrations.RunPython(create_blue_state, create_blue_group),
migrations.RunPython(populate_ownerships, migrations.RunPython.noop),
migrations.RunPython(create_profiles, recreate_authservicesinfo),
migrations.RemoveField(
model_name='authservicesinfo',
name='user',
),
migrations.DeleteModel(
name='AuthServicesInfo',
),
]

View File

@@ -1,40 +1,24 @@
from __future__ import unicode_literals
from django.utils.encoding import python_2_unicode_compatible
from django.db import models
from django.contrib.auth.models import User
from authentication.states import MEMBER_STATE, BLUE_STATE, NONE_STATE
from eveonline.models import EveCharacter
@python_2_unicode_compatible
class AuthServicesInfo(models.Model):
class Meta:
default_permissions = ('change',)
STATE_CHOICES = (
(NONE_STATE, 'None'),
(BLUE_STATE, 'Blue'),
(MEMBER_STATE, 'Member'),
)
main_char_id = models.CharField(max_length=64, blank=True, default="")
user = models.OneToOneField(User)
state = models.CharField(blank=True, null=True, choices=STATE_CHOICES, default=NONE_STATE, max_length=10)
def __str__(self):
return self.user.username + ' - AuthInfo'
from django.contrib.auth.models import User, Permission
from authentication.managers import CharacterOwnershipManager, StateManager
from eveonline.models import EveCharacter, EveCorporationInfo, EveAllianceInfo
@python_2_unicode_compatible
class State(models.Model):
name = models.CharField(_('name'), max_length=20, unique=True)
permissions = models.ManyToManyField(
Permission,
verbose_name=_('permissions'),
blank=True,
)
name = models.CharField(max_length=20, unique=True)
permissions = models.ManyToManyField(Permission, blank=True)
priority = models.IntegerField(unique=True)
member_characters = models.ManyToManyField(EveCharacter)
member_corporations = models.ManyToManyField(EveCorporationInfo)
member_alliances = models.ManyToManyField(EveAllianceInfo)
public = models.BooleanField(default=False)
objects = StateManager()
class Meta:
ordering = ['priority']
@@ -42,8 +26,8 @@ class State(models.Model):
return self.name
def get_none_state():
return State.objects.get_or_create(name='None')[0]
def get_guest_state():
return State.objects.update_or_create(name='Guest', defaults={'priority': 0, 'public': True})[0]
@python_2_unicode_compatible
@@ -52,7 +36,28 @@ class UserProfile(models.Model):
default_permissions = ('change',)
user = models.OneToOneField(User, related_name='profile', on_delete=models.CASCADE)
main_character = models.ForeignKey(EveCharacter, on_delete=models.CASCADE)
state = models.ForeignKey(State, on_delete=models.SET(get_none_state))
main_character = models.OneToOneField(EveCharacter, blank=True, null=True, on_delete=models.SET_NULL)
state = models.ForeignKey(State, on_delete=models.SET(get_guest_state))
def assign_state(self, commit=True):
self.state = State.objects.get_for_user(self.user)
if commit:
self.save(update_fields=['state'])
def __str__(self):
return "%s Profile" % self.user
@python_2_unicode_compatible
class CharacterOwnership(models.Model):
class Meta:
default_permissions = ('change', 'delete')
character = models.OneToOneField(EveCharacter, on_delete=models.CASCADE, related_name='character_ownership')
owner_hash = models.CharField(max_length=28, unique=True)
owner_token = models.ForeignKey(Token, on_delete=models.SET_NULL)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='character_ownerships')
objects = CharacterOwnershipManager()
def __str__(self):
return "%s: %s" % (self.user, self.character)

View File

@@ -1,34 +1,70 @@
from __future__ import unicode_literals
from django.db.models.signals import pre_save, post_save
from django.db.models.signals import post_save, pre_delete
from django.dispatch import receiver
from django.contrib.auth.models import User
from authentication.models import AuthServicesInfo
from authentication.states import MEMBER_STATE, BLUE_STATE
from authentication.tasks import make_member, make_blue, disable_member
from authentication.models import CharacterOwnership, UserProfile, get_guest_state
from services.tasks import validate_services
from esi.models import Token
from eveonline.managers import EveManager
from eveonline.models import EveCharacter
import logging
logger = logging.getLogger(__name__)
@receiver(pre_save, sender=AuthServicesInfo)
def pre_save_auth_state(sender, instance, *args, **kwargs):
if instance.pk:
old_instance = AuthServicesInfo.objects.get(pk=instance.pk)
if old_instance.state != instance.state:
logger.debug('Detected state change for %s' % instance.user)
if instance.state == MEMBER_STATE:
make_member(instance)
elif instance.state == BLUE_STATE:
make_blue(instance)
else:
disable_member(instance.user)
validate_services.apply(args=(instance.user,))
# Is there a smarter way to intercept pre_save with a diff main_character or state?
@receiver(post_save, sender=UserProfile)
def reassess_on_profile_save(sender, instance, created, *args, **kwargs):
# catches post_save from profiles to trigger necessary service and state checks
if not created:
update_fields = kwargs.pop('update_fields', [])
if 'state' not in update_fields:
instance.assign_state()
# TODO: how do we prevent running this twice on profile state change?
validate_services(instance.user)
@receiver(post_save, sender=User)
def post_save_user(sender, instance, created, *args, **kwargs):
def create_required_models(sender, instance, created, *args, **kwargs):
# ensure all users have a model
if created:
AuthServicesInfo.objects.get_or_create(user=instance)
UserProfile.objects.get_or_create(user=instance)
@receiver(post_save, sender=Token)
def record_character_ownership(sender, instance, created, *args, **kwargs):
if created:
# purge ownership records if the hash or auth user account has changed
CharacterOwnership.objects.filter(character__character_id=instance.character_id).exclude(
owner_hash=instance.owner_hash).exclude(user=instance.user).delete()
# create character if needed
if EveCharacter.objects.filter(character_id=instance.character_id).exists() is False:
EveManager.create_character(instance.character_id)
char = EveCharacter.objects.get(character_id=instance.character_id)
CharacterOwnership.objects.update_or_create(character=char,
defaults={'owner_hash': instance.owner_hash, 'user': instance.user})
@receiver(pre_delete, sender=CharacterOwnership)
def validate_main_character(sender, instance, *args, **kwargs):
if instance.user.profile.main_character == instance.character:
# clear main character as user no longer owns them
instance.user.profile.main_character = None
instance.user.profile.save()
@receiver(pre_delete, sender=Token)
def validate_main_character_token(sender, instance, *args, **kwargs):
if UserProfile.objects.filter(main_character__character_id=instance.character_id):
if not Token.objects.filter(character_id=instance.character_id).filter(user=instance.user).exists():
# clear main character as we can no longer verify ownership
instance.user.profile.main_character = None
instance.user.profile.save()
@receiver(post_save, sender=User)
def assign_state_on_reactivate(sender, instance, *args, **kwargs):
# There's no easy way to trigger an action upon saving from pre_save signal
# If we're saving a user and that user is in the Guest state, assume is_active was just set to True and assign state
if instance.is_active and instance.profile.state == get_guest_state():
instance.profile.assign_state()

View File

@@ -1,4 +0,0 @@
from __future__ import unicode_literals
MEMBER_STATE = 'Member'
BLUE_STATE = 'Blue'
NONE_STATE = None

View File

@@ -1,189 +0,0 @@
from __future__ import unicode_literals
from services.tasks import validate_services
from django.contrib.auth.models import Group
from authentication.models import AuthServicesInfo
from authentication.states import MEMBER_STATE, BLUE_STATE, NONE_STATE
from eveonline.models import EveCharacter, EveCorporationInfo
from notifications import notify
from django.conf import settings
import logging
logger = logging.getLogger(__name__)
def generate_corp_group_name(corpname):
return 'Corp_' + corpname.replace(' ', '_')
def generate_alliance_group_name(alliancename):
return 'Alliance_' + alliancename.replace(' ', '_')
def disable_member(user):
"""
Disable a member who is transitioning to a NONE state.
:param user: django.contrib.auth.models.User to disable
:return:
"""
logger.debug("Disabling member %s" % user)
if user.user_permissions.all().exists():
logger.info("Clearning user %s permission to deactivate user." % user)
user.user_permissions.clear()
if user.groups.all().exists():
logger.info("Clearing all non-public user %s groups to disable member." % user)
user.groups.remove(*user.groups.filter(authgroup__public=False))
validate_services.apply(args=(user,))
def disable_user(user):
"""
Disable a user who is being set inactive or deleted
:param user: django.contrib.auth.models.User to disable
:return:
"""
logger.debug("Disabling user %s" % user)
if user.user_permissions.all().exists():
logger.info("Clearning user %s permission to deactivate user." % user)
user.user_permissions.clear()
if user.groups.all().exists():
logger.info("Clearing user %s groups to deactivate user." % user)
user.groups.clear()
validate_services.apply(args=(user,))
def make_member(auth):
logger.debug("Ensuring user %s has member permissions and groups." % auth.user)
# ensure member is not blue right now
blue_group, c = Group.objects.get_or_create(name=settings.DEFAULT_BLUE_GROUP)
if blue_group in auth.user.groups.all():
logger.info("Removing user %s blue group" % auth.user)
auth.user.groups.remove(blue_group)
# make member
member_group, c = Group.objects.get_or_create(name=settings.DEFAULT_AUTH_GROUP)
if member_group not in auth.user.groups.all():
logger.info("Adding user %s to member group" % auth.user)
auth.user.groups.add(member_group)
assign_corp_group(auth)
assign_alliance_group(auth)
def make_blue(auth):
logger.debug("Ensuring user %s has blue permissions and groups." % auth.user)
# ensure user is not a member
member_group, c = Group.objects.get_or_create(name=settings.DEFAULT_AUTH_GROUP)
if member_group in auth.user.groups.all():
logger.info("Removing user %s member group" % auth.user)
auth.user.groups.remove(member_group)
# make blue
blue_group, c = Group.objects.get_or_create(name=settings.DEFAULT_BLUE_GROUP)
if blue_group not in auth.user.groups.all():
logger.info("Adding user %s to blue group" % auth.user)
auth.user.groups.add(blue_group)
assign_corp_group(auth)
assign_alliance_group(auth)
def determine_membership_by_character(char):
if str(char.corporation_id) in settings.STR_CORP_IDS:
logger.debug("Character %s in member corp id %s" % (char, char.corporation_id))
return MEMBER_STATE
elif str(char.alliance_id) in settings.STR_ALLIANCE_IDS:
logger.debug("Character %s in member alliance id %s" % (char, char.alliance_id))
return MEMBER_STATE
elif not EveCorporationInfo.objects.filter(corporation_id=char.corporation_id).exists():
logger.debug("No corp model for character %s corp id %s. Unable to check standings. Non-member." % (
char, char.corporation_id))
return NONE_STATE
else:
corp = EveCorporationInfo.objects.get(corporation_id=char.corporation_id)
if corp.is_blue:
logger.debug("Character %s member of blue corp %s" % (char, corp))
return BLUE_STATE
else:
logger.debug("Character %s member of non-blue corp %s. Non-member." % (char, corp))
return NONE_STATE
def determine_membership_by_user(user):
logger.debug("Determining membership of user %s" % user)
auth = AuthServicesInfo.objects.get(user=user)
if auth.main_char_id:
if EveCharacter.objects.filter(character_id=auth.main_char_id).exists():
char = EveCharacter.objects.get(character_id=auth.main_char_id)
return determine_membership_by_character(char)
else:
logger.debug("Character model matching user %s main character id %s does not exist. Non-member." % (
user, auth.main_char_id))
return NONE_STATE
else:
logger.debug("User %s has no main character set. Non-member." % user)
return NONE_STATE
def set_state(user):
if user.is_active:
state = determine_membership_by_user(user)
else:
state = NONE_STATE
logger.debug("Assigning user %s to state %s" % (user, state))
auth = AuthServicesInfo.objects.get(user=user)
if auth.state != state:
auth.state = state
auth.save()
notify(user, "Membership State Change", message="You membership state has been changed to %s" % state)
assign_corp_group(auth)
assign_alliance_group(auth)
def assign_corp_group(auth):
corp_group = None
if auth.main_char_id:
if EveCharacter.objects.filter(character_id=auth.main_char_id).exists():
char = EveCharacter.objects.get(character_id=auth.main_char_id)
corpname = generate_corp_group_name(char.corporation_name)
if auth.state == BLUE_STATE and settings.BLUE_CORP_GROUPS:
logger.debug("Validating blue user %s has corp group assigned." % auth.user)
corp_group, c = Group.objects.get_or_create(name=corpname)
elif auth.state == MEMBER_STATE and settings.MEMBER_CORP_GROUPS:
logger.debug("Validating member %s has corp group assigned." % auth.user)
corp_group, c = Group.objects.get_or_create(name=corpname)
else:
logger.debug("Ensuring %s has no corp groups assigned." % auth.user)
if corp_group:
if corp_group not in auth.user.groups.all():
logger.info("Adding user %s to corp group %s" % (auth.user, corp_group))
auth.user.groups.add(corp_group)
for g in auth.user.groups.all():
if str.startswith(str(g.name), "Corp_"):
if g != corp_group:
logger.info("Removing user %s from old corpgroup %s" % (auth.user, g))
auth.user.groups.remove(g)
def assign_alliance_group(auth):
alliance_group = None
if auth.main_char_id:
if EveCharacter.objects.filter(character_id=auth.main_char_id).exists():
char = EveCharacter.objects.get(character_id=auth.main_char_id)
if char.alliance_name:
alliancename = generate_alliance_group_name(char.alliance_name)
if auth.state == BLUE_STATE and settings.BLUE_ALLIANCE_GROUPS:
logger.debug("Validating blue user %s has alliance group assigned." % auth.user)
alliance_group, c = Group.objects.get_or_create(name=alliancename)
elif auth.state == MEMBER_STATE and settings.MEMBER_ALLIANCE_GROUPS:
logger.debug("Validating member %s has alliance group assigned." % auth.user)
alliance_group, c = Group.objects.get_or_create(name=alliancename)
else:
logger.debug("Ensuring %s has no alliance groups assigned." % auth.user)
else:
logger.debug("User %s main character %s not in an alliance. Ensuring no alliance group assigned." % (
auth.user, char))
if alliance_group:
if alliance_group not in auth.user.groups.all():
logger.info("Adding user %s to alliance group %s" % (auth.user, alliance_group))
auth.user.groups.add(alliance_group)
for g in auth.user.groups.all():
if str.startswith(str(g.name), "Alliance_"):
if g != alliance_group:
logger.info("Removing user %s from old alliance group %s" % (auth.user, g))
auth.user.groups.remove(g)

View File

@@ -0,0 +1,94 @@
{% extends "registered/base.html" %}
{% load staticfiles %}
{% load i18n %}
{% block title %}{% trans "Dashboard" %}{% endblock %}
{% block content %}
<div class="col-lg-12">
<h1 class="page-header text-center">{% trans "Dashboard" %}</h1>
<div class="col-lg-12 container">
<div class="row">
<div class="col-lg-6 text-center">
<div class="panel panel-primary">
<div class="panel-heading">{% trans "Main Character" %}</div>
<div class="panel-body">
{% if request.user.profile.main_character %}
{% with request.user.profile.main_character as main %}
<div class="col-lg-4 col-sm-2">
<table class="table">
<tr><td class="text-center"><img class="ra-avatar" src="https://image.eveonline.com/Character/{{ main.character_id }}_128.jpg"></td></tr>
<tr><td class="text-center">{{ main.character_name }}</td></tr>
</table>
</div>
<div class="col-lg-4 col-sm-2">
<table class="table">
<tr><td class="text-center"><img class="ra-avatar" src="https://image.eveonline.com/Corporation/{{ main.corporation_id }}_128.png"></td></tr>
<tr><td class="text-center">{{ main.corporation_name }}</td></tr>
</table>
</div>
<div class="col-lg-4 col-sm-2">
{% if main.alliance_id %}
<table class="table">
<tr><td class="text-center"><img class="ra-avatar" src="https://image.eveonline.com/Alliance/{{ main.alliance_id }}_128.png"></td></tr>
<tr><td class="text-center">{{ main.alliance_name }}</td><tr>
</table>
{% endif %}
</div>
{% endwith %}
{% else %}
<div class="alert alert-danger" role="alert">{% trans "Missing main character model." %}</div>
{% endif %}
<div class="clearfix"></div>
<div class="col-xs-6">
<a href="{% url 'authentication:add_character' %}" class="btn btn-block btn-info" title="Add Character">{% trans 'Add Character' %}</a>
</div>
<div class="col-xs-6">
<a href="{% url 'authentication:change_main_character' %}" class="btn btn-block btn-info" title="Change Main Character">{% trans "Change Main" %}</a>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-6 text-center">
<div class="panel panel-success">
<div class="panel-heading">{% trans "Groups" %}</div>
<div class="panel-body">
<div style="height: 236px;overflow:-moz-scrollbars-vertical;overflow-y:auto;">
<table class="table table-striped">
{% for group in user.groups.all %}
<tr>
<td>{{ group.name }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
</div>
</div>
</div>
<div class="clearfix"></div>
<div class="panel panel-default">
<div class="panel-heading" style="display:flex;">{% trans 'Characters' %}</div>
<div class="panel-body">
<table class="table table-hover">
<tr>
<th class="text-center"></th>
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Corp' %}</th>
<th class="text-center">{% trans 'Alliance' %}</th>
</tr>
{% for ownership in request.user.character_ownerships.all %}
{% with ownership.character as char %}
<tr>
<td class="text-center">{{ char.character_name }}</td>
<td class="text-center">{{ char.corporation_name }}</td>
<td class="text-center">{{ char.alliance_name }}</td>
</tr>
{% endwith %}
{% endfor %}
</table>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,49 @@
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<title>{% block title %}{{ SITE_NAME }}{% endblock %}</title>
{% include 'bundles/bootstrap-css.html' %}
{% include 'bundles/fontawesome.html' %}
{% block extra_include %}
{% endblock %}
<style>
body {
background: url('{% static 'img/index_images/index_blank_bg.jpg' %}') no-repeat scroll;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
}
.panel-transparent {
background: rgba(48, 48, 48, 0.7);
color: #ffffff;
}
.panel-body {
}
#lang_select {
width: 40%;
margin-left: auto;
margin-right: auto;
}
{% block extra_style %}
{% endblock %}
</style>
</head>
<body>
<div class="container" style="margin-top:150px">
{% block content %}
{% endblock %}
</div>
</body>
</html>

View File

@@ -0,0 +1,15 @@
{% load i18n %}
<div class="dropdown">
<form action="{% url 'set_language' %}" method="post">
{% csrf_token %}
<input name="next" type="hidden" value="{{ request.get_full_path|slice:'3:' }}" />
<select onchange="this.form.submit()" class="form-control" id="lang_select" name="language">
{% get_language_info_list for LANGUAGES as languages %}
{% for language in languages %}
<option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected="selected"{% endif %}>
{{ language.name_local }} ({{ language.code }})
</option>
{% endfor %}
</select>
</form>
</div>

View File

@@ -0,0 +1,27 @@
{% extends 'public/base.html' %}
{% block title %}Login{% endblock %}
{% block content %}
<div class="col-md-4 col-md-offset-4">
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.level_tag}}">{{ message }}</div>
{% endfor %}
{% endif %}
<div class="panel panel-default panel-transparent">
<div class="panel-body">
<div class="col-md-12">
<p style="text-align:center">
<a href="{% url 'auth_sso_login' %}">
<img src="{% static 'img/sso/EVE_SSO_Login_Buttons_Large_Black.png' %}" border=0>
</a>
</p>
</div>
</div>
</div>
{% include 'public/lang_select.html' %}
</div>
{% endblock %}
{% block extra_include %}
{% include 'bundles/bootstrap-js.html' %}
{% endblock %}

View File

@@ -0,0 +1,24 @@
{% load staticfiles %}
{% load bootstrap %}
{% load i18n %}
{% extends 'public/base.html' %}
{% block title %}Registration{% endblock %}
{% block extra_include %}
{% include 'bundles/bootstrap-css.html' %}
{% include 'bundles/fontawesome.html' %}
{% include 'bundles/bootstrap-js.html' %}
{% endblock %}
{% block content %}
<div class="col-md-4 col-md-offset-4">
<div class="panel panel-default panel-transparent">
<div class="panel-body">
<form method="POST">
{% csrf_token %}
{{ form|bootstrap }}
<button class="btn btn-lg btn-primary btn-block" type="submit">{% trans "Register" %}</button>
</form>
</div>
</div>
{% include 'public/lang_select.html' %}
</div>
{% endblock %}

View File

@@ -0,0 +1,215 @@
{% load static %}
{% load i18n %}
{% load navactive %}
{% load menu_items %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<title>{% block title %}{% block page_title %}{% endblock page_title %} - Alliance Auth{% endblock title %}</title>
{% include 'bundles/bootstrap-css.html' %}
{% include 'bundles/fontawesome.html' %}
<link href="{% static 'css/auth-base.css' %}" type="text/css" rel="stylesheet">
{% block extra_css %}{% endblock extra_css %}
</head>
<body>
{% if user.is_authenticated %}
<div id="wrapper">
<!-- Navigation -->
<nav class="navbar navbar-inverse navbar-static-top auth-navbar-top" role="navigation">
<div class="container-fluid">
<a class="navbar-brand">
{{ SITE_NAME }}
</a>
<!-- /.navbar-header -->
<ul class="nav navbar-top-links navbar-right">
<li class="nav-link">
<form id="f_lang_select" action="{% url 'set_language' %}" method="post">
{% csrf_token %}
<input name="next" type="hidden" value="{{ request.get_full_path|slice:'3:' }}" />
<select onchange="this.form.submit()" class="form-control" id="lang_select" name="language">
{% get_language_info_list for LANGUAGES as languages %}
{% for language in languages %}
<option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected="selected"{% endif %}>
{{ language.name_local }} ({{ language.code }})
</option>
{% endfor %}
</select>
</form>
</li>
{% if notifications %}
<li class="nav-link active">
<a href="{% url 'auth_notification_list' %}">
<span class="fa-stack">
<i class="fa fa-bell fa-stack-2x"></i>
<i class="fa fa-inverse fa-stack-1x">{{ notifications }}</i>
</span>
</a>
</li>
{% else %}
<li class="nav-link"><a href="{% url 'auth_notification_list' %}">
<i class="fa fa-bell-o"></i></a>
</li>
{% endif %}
{% if user.is_authenticated %}
{% if user.is_staff %}
<li><a href="{% url 'admin:index' %}">{% trans "Admin" %}</a></li>
{% endif %}
<li><a href="{% url 'auth_logout_user' %}">{% trans "Logout" %}</a></li>
{% else %}
<li><a href="{% url 'auth_login_user' %}">{% trans "Login" %}</a></li>
{% endif %}
</ul>
</div>
<!-- /.navbar-top-links -->
<div class="navbar-default sidebar auth-sidebar" role="navigation">
<div class="sidebar-nav navbar-collapse">
<ul class="nav" id="side-menu">
<li class="text-center divider-horizontal">
<h5>{% trans "Main Navigation" %}</h5>
</li>
<li>
<a class="{% navactive request 'auth_dashboard' %}" href="{% url 'auth_dashboard' %}">
<i class="fa fa-dashboard fa-fw grayiconecolor"></i>{% trans " Dashboard" %}
</a>
</li>
<li>
<a class="{% navactive request 'auth_groups' %}" href="{% url 'auth_groups' %}">
<i class="fa fa-cogs fa-fw fa-sitemap grayiconecolor"></i>{% trans " Groups" %}
</a>
</li>
<li>
<a class="{% navactive request 'auth_help' %}" href="{% url 'auth_help' %}">
<i class="fa fa-question fa-fw grayiconecolor"></i>{% trans " Help" %}
</a>
</li>
{% menu_main %}
<li class="text-center divider-horizontal">
<h5>{% trans "Aux Navigation" %}</h5>
</li>
<li>
<a class="{% navactive request 'auth_services' %}" href="{% url 'auth_services' %}">
<i class="fa fa-cogs fa-fw grayiconecolor"></i>{% trans " Services" %}
</a>
</li>
<li>
<a class="{% navactive request 'auth_hrapplications_view auth_hrapplication_create_view auth_hrapplication_personal_view auth_hrapplication_search auth_hrapplication_view' %}"
href="{% url 'auth_hrapplications_view' %}">
<i class="fa fa-file-o fa-fw grayiconecolor"></i>{% trans " Applications" %}
</a>
</li>
{% if perms.corputils.view_corp_corpstats or perms.corputils.view_alliance_corpstats or perms.corputils.view_blue_corpstats %}
<li>
<a class="{% navactive request 'corputils:view corputils:search' %}" href="{% url 'corputils:view' %}">
<i class="fa fa-share-alt fa-fw grayiconecolor"></i>{% trans " Corporation Stats" %}
</a>
</li>
{% endif %}
{% if can_manage_groups %}
<li>
<a class="{% navactive request 'auth_group_management auth_group_membership auth_group_membership_list' %}" href="{% url 'auth_group_management' %}">
<i class="fa fa-lock fa-sitemap fa-fw grayiconecolor"></i>{% trans " Group Management" %}
</a>
</li>
{% endif %}
{% if perms.auth.view_fleetup %}
<li>
<a class="{% navactive request 'auth_fleetup_view auth_fleetup_fittings auth_fleetup_fitting auth_fleetup_doctrines auth_fleetup_doctrine auth_fleetup_characters' %}" href="{% url 'auth_fleetup_view' %}">
<i class="fa fa-clock-o fa-fw grayiconecolor"></i> Fleet-Up
</a>
</li>
{% endif %}
{% if perms.auth.optimer_view %}
<li>
<a class="{% navactive request 'auth_optimer_view auth_add_optimer_view auth_edit_optimer' %}" href="{% url 'auth_optimer_view' %}">
<i class="fa fa-exclamation fa-fw grayiconecolor"></i>{% trans " Fleet Operations" %}
</a>
</li>
{% endif %}
{% if perms.auth.timer_view %}
<li>
<a class="{% navactive request 'auth_timer_view auth_add_timer_view auth_edit_timer' %}" href="{% url 'auth_timer_view' %}">
<i class="fa fa-clock-o fa-fw grayiconecolor"></i>{% trans " Structure Timers" %}
</a>
</li>
{% endif %}
<li>
<a class="{% navactive request 'auth_fatlink_view auth_fatlink_view_statistics auth_fatlink_view_statistics_month auth_fatlink_view_personal_statistics auth_fatlink_view_personal_statistics_year auth_fatlink_view_personal_statistics_month auth_fatlink_view_user_statistics_month auth_create_fatlink_view auth_modify_fatlink_view auth_click_fatlink_view' %}" href="{% url 'auth_fatlink_view' %}">
<i class="fa fa-users fa-lightbulb-o fa-fw grayiconecolor"></i>{% trans " Fleet Activity Tracking" %}
</a>
</li>
<li>
<a class="{% navactive request 'auth_srp_management_view auth_srp_management_all_view auth_srp_fleet_view auth_srp_fleet_add_view auth_srp_fleet_edit_view auth_srp_request_view auth_srp_request_update_amount_view' %}" href="{% url 'auth_srp_management_view' %}">
<i class="fa fa-money fa-fw grayiconecolor"></i>{% trans " Ship Replacement" %}
</a>
</li>
{% menu_aux %}
<li class="text-center divider-horizontal">
<h5>{% trans "Util" %}</h5>
</li>
{% if perms.auth.jabber_broadcast or perms.auth.jabber_broadcast_all or user.is_superuser %}
<li>
<a class="{% navactive request 'auth_fleet_format_tool_view' %}" href="{% url 'auth_fleet_format_tool_view' %}">
<i class="fa fa-space-shuttle fa-fw grayiconecolor"></i>{% trans " Fleet Broadcast Formatter" %}
</a>
</li>
{% endif %}
{% menu_util %}
</ul>
</div>
<!-- /.sidebar-collapse -->
</div>
<!-- /.navbar-static-side -->
</nav>
<div id="page-wrapper">
<div class="container">
{% if messages %}
<br>
{% for message in messages %}
<div class="alert alert-{{ message.level_tag}}">{{ message }}</div>
{% endfor %}
{% endif %}
{% block content %}
{% endblock content %}
</div>
</div>
</div>
{% endif %}
{% include 'bundles/bootstrap-js.html' %}
{% block extra_javascript %}
{% endblock extra_javascript %}
<script>
{% block extra_script %}
{% endblock extra_script %}
</script>
</body>
</html>

View File

@@ -0,0 +1,24 @@
{% extends "registered/base.html" %}
{% load bootstrap %}
{% load i18n static %}
{% block title %}{% trans 'Password Change' %}{% endblock %}
{% block content %}
<div class="col-lg-12">
<h1 class="page-header text-center">{% trans "Change Password" %}</h1>
<div class="container-fluid">
<div class="col-md-4 col-md-offset-4">
<div class="row">
<p class="text-center">
{% trans "Completed" %}
</p>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,26 @@
{% extends "registered/base.html" %}
{% load bootstrap %}
{% load i18n static %}
{% block title %}{% trans "Password Change" %}{% endblock %}
{% block content %}
<div class="col-lg-12">
<h1 class="page-header text-center">{% trans "Change Password" %}</h1>
<div class="container-fluid">
<div class="col-md-4 col-md-offset-4">
<div class="row">
<form class="form-signin" role="form" action="" method="POST">
{% csrf_token %}
{{ form|bootstrap }}
<br/>
<button class="btn btn-lg btn-primary btn-block" type="submit">{% trans "Change Password" %}</button>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,44 @@
{% load staticfiles %}
{% load i18n %}
{% load bootstrap %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<title>{{ SITE_NAME }} - {% block page_title %}{% endblock page_title %}</title>
{% include 'bundles/bootstrap-css.html' %}
{% include 'bundles/fontawesome.html' %}
<style>
body {
background: url('{% static 'img/index_images/index_blank_bg.jpg' %}') no-repeat scroll;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
}
.panel-transparent {
background: rgba(48, 48, 48, 0.7);
color: #ffffff;
}
.panel-body {
}
</style>
</head>
<body>
{% block content %}
{% endblock content %}
{% include 'bundles/bootstrap-js.html' %}
</body>
</html>

View File

@@ -0,0 +1,22 @@
{% extends 'registration/password_reset_base.html' %}
{% load staticfiles %}
{% load i18n %}
{% load bootstrap %}
{% block page_title %}Login{% endblock %}
{% block content %}
<div class="container" style="margin-top:150px">
<div class="col-md-4 col-md-offset-4">
<div class="panel panel-default panel-transparent">
<div class="panel-body">
<h1 class="text-center">{% trans 'Password reset complete' %}</h1>
<p class="text-center">{% trans "Your password has been set." %}</p>
<a href="{{ login_url }}" class="btn btn-lg btn-success btn-block">{% trans "Login" %}</a>
</div>
</div>
</div>
</div>
{% endblock content %}

View File

@@ -0,0 +1,32 @@
{% extends 'registration/password_reset_base.html' %}
{% load staticfiles %}
{% load i18n %}
{% load bootstrap %}
{% block page_title %}Login{% endblock %}
{% block content %}
<div class="container" style="margin-top:150px">
<div class="col-md-4 col-md-offset-4">
<div class="panel panel-default panel-transparent">
<div class="panel-body">
{% if validlink %}
<form class="form-signin" role="form" action="" method="POST">
{% csrf_token %}
{{ form |bootstrap }}
<div class="">
<button class="btn btn-lg btn-primary btn-block" type="submit">{% trans "Change Password" %}</button>
</div>
</form>
{% else %}
<h1>{% trans 'Password reset unsuccessful' %}</h1>
<p>{% trans "The password reset link was invalid, possibly because it has already been used. Please request a new password reset." %}</p>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,23 @@
{% extends 'registration/password_reset_base.html' %}
{% load staticfiles %}
{% load i18n %}
{% load bootstrap %}
{% block page_title %}Login{% endblock %}
{% block content %}
<div class="container" style="margin-top:150px">
<div class="col-md-4 col-md-offset-4">
<div class="panel panel-default panel-transparent">
<div class="panel-body">
<h1 class="text-center">{% trans 'Password Reset Success' %}</h1>
<p>{% trans "We've emailed you instructions for setting your password. You should be receiving them shortly." %}</p>
<p>{% trans "If you don't receive an email, please make sure you've entered the address you registered with, and check your spam folder." %}</p>
</div>
</div>
</div>
</div>
{% endblock content %}

View File

@@ -0,0 +1,15 @@
{% load i18n %}{% autoescape off %}
{% blocktrans %}You're receiving this email because you requested a password reset for your
user account.{% endblocktrans %}
{% trans "Please go to the following page and choose a new password:" %}
{% block reset_link %}
{{domain}}{% url 'password_reset_confirm' uidb64=uid token=token %}
{% endblock %}
{% trans "Your username, in case you've forgotten:" %} {{ user.get_username }}
{% trans "Thanks for using our site!" %}
{% blocktrans %}Your IT Team{% endblocktrans %}
{% endautoescape %}

View File

@@ -0,0 +1,27 @@
{% extends 'registration/password_reset_base.html' %}
{% load staticfiles %}
{% load i18n %}
{% load bootstrap %}
{% block page_title %}Login{% endblock %}
{% block content %}
<div class="container" style="margin-top:150px">
<div class="col-md-4 col-md-offset-4">
<div class="panel panel-default panel-transparent">
<div class="panel-body">
<form class="form-signin" role="form" action="" method="POST">
{% csrf_token %}
<h1 class="text-center">{% trans "Password Reset" %}</h1>
<p class="text-center">{% trans "Forgotten your password? Enter your email below." %}</p>
{{ form|bootstrap }}
<div class="">
<button class="btn btn-lg btn-primary btn-block" type="submit">{% trans "Reset Password" %}</button>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock content %}

View File

@@ -1,84 +0,0 @@
from __future__ import unicode_literals
try:
# Py3
from unittest import mock
except ImportError:
# Py2
import mock
from django.test import TestCase
from django.contrib.auth.models import Group, Permission
from alliance_auth.tests.auth_utils import AuthUtils
from authentication.tasks import disable_member, disable_user
class AuthenticationTasksTestCase(TestCase):
def setUp(self):
self.member = AuthUtils.create_member('auth_member')
self.none_user = AuthUtils.create_user('none_user', disconnect_signals=True)
@mock.patch('services.signals.transaction')
def test_disable_member(self, transaction):
# Inert signals action
transaction.on_commit.side_effect = lambda fn: fn()
# Add permission
perm = Permission.objects.create(codename='test_perm', name='test perm', content_type_id=1)
# Add public group
pub_group = Group.objects.create(name="A Public group")
pub_group.authgroup.internal = False
pub_group.authgroup.public = True
pub_group.save()
# Setup member
self.member.user_permissions.add(perm)
self.member.groups.add(pub_group)
# Pre assertion
self.assertIn(pub_group, self.member.groups.all())
self.assertGreater(len(self.member.groups.all()), 1)
# Act
disable_member(self.member)
# Assert
self.assertIn(pub_group, self.member.groups.all())
# Everything but the single public group wiped
self.assertEqual(len(self.member.groups.all()), 1)
# All permissions wiped
self.assertEqual(len(self.member.user_permissions.all()), 0)
@mock.patch('services.signals.transaction')
def test_disable_user(self, transaction):
# Inert signals action
transaction.on_commit.side_effect = lambda fn: fn()
# Add permission
perm = Permission.objects.create(codename='test_perm', name='test perm', content_type_id=1)
# Add public group
pub_group = Group.objects.create(name="A Public group")
pub_group.authgroup.internal = False
pub_group.authgroup.public = True
pub_group.save()
# Setup member
self.member.user_permissions.add(perm)
self.member.groups.add(pub_group)
# Pre assertion
self.assertIn(pub_group, self.member.groups.all())
self.assertGreater(len(self.member.groups.all()), 1)
# Act
disable_user(self.member)
# Assert
# All groups wiped
self.assertEqual(len(self.member.groups.all()), 0)
# All permissions wiped
self.assertEqual(len(self.member.user_permissions.all()), 0)

21
authentication/urls.py Normal file
View File

@@ -0,0 +1,21 @@
from django.conf.urls import url, include
from django.contrib.auth.decorators import login_required
from django.views.generic.base import TemplateView
from authentication import views
from registration.backends.hmac import urls
# inject our custom view classes into the HMAC scheme but use their urlpatterns because :efficiency:
urls.views = views
app_name = 'authentication'
urlpatterns = [
url(r'^$', TemplateView.as_view(template_name='public/login.html'), name='login'),
url(r'^account/login/$', TemplateView.as_view(template_name='public/login.html')),
url(r'^account/characters/main/$', views.main_character_change, name='change_main_character'),
url(r'^account/characters/add/$', views.add_character, name='add_character'),
url(r'^account/', include(urls, namespace='registration')),
url(r'^help/$', login_required(TemplateView.as_view(template_name='public/help.html')), name='help'),
url(r'^dashboard/$',
login_required(TemplateView.as_view(template_name='authentication/dashboard.html')), name='dashboard'),
]

View File

@@ -1,116 +1,123 @@
from __future__ import unicode_literals
from django.contrib.auth import login
from django.contrib.auth import logout
from django.contrib.auth import authenticate
from django.shortcuts import render, redirect
from django.contrib.auth import login, authenticate
from django.shortcuts import redirect
from django.contrib.auth.decorators import login_required
from django.utils.translation import ugettext_lazy as _
from eveonline.managers import EveManager
from eveonline.models import EveCharacter
from authentication.models import AuthServicesInfo
from authentication.forms import LoginForm, RegistrationForm
from django.contrib.auth.models import User
from authentication.forms import RegistrationForm
from authentication.models import CharacterOwnership
from django.contrib import messages
from django.contrib.auth.models import User
from django.conf import settings
from django.core import signing
from esi.decorators import token_required
from registration.backends.hmac.views import RegistrationView as BaseRegistrationView, \
ActivationView as BaseActivationView, REGISTRATION_SALT
from registration.signals import user_registered
import logging
logger = logging.getLogger(__name__)
def login_user(request):
logger.debug("login_user called by user %s" % request.user)
if request.method == 'POST':
form = LoginForm(request.POST)
logger.debug("Request of type POST, received form, valid: %s" % form.is_valid())
if form.is_valid():
user = authenticate(username=form.cleaned_data['username'], password=form.cleaned_data['password'])
logger.debug("Authentication attempt with supplied credentials. Received user %s" % user)
if user is not None:
if user.is_active:
logger.info("Successful login attempt from user %s" % user)
login(request, user)
redirect_to = request.POST.get('next', request.GET.get('next', ''))
if not redirect_to:
redirect_to = 'auth_dashboard'
return redirect(redirect_to)
else:
logger.info("Login attempt failed for user %s: user marked inactive." % user)
messages.warning(request, _('Your account has been disabled.'))
else:
logger.info("Failed login attempt: provided username %s" % form.cleaned_data['username'])
messages.error(request, _('Username/password invalid.'))
return render(request, 'public/login.html', context={'form': form})
else:
logger.debug("Providing new login form.")
form = LoginForm()
return render(request, 'public/login.html', context={'form': form})
def logout_user(request):
logger.debug("logout_user called by user %s" % request.user)
temp_user = request.user
logout(request)
logger.info("Successful logout for user %s" % temp_user)
return redirect("auth_index")
def register_user_view(request):
logger.debug("register_user_view called by user %s" % request.user)
if request.method == 'POST':
form = RegistrationForm(request.POST)
logger.debug("Request type POST contains form valid: %s" % form.is_valid())
if form.is_valid():
if not User.objects.filter(username=form.cleaned_data['username']).exists():
user = User.objects.create_user(form.cleaned_data['username'],
form.cleaned_data['email'], form.cleaned_data['password'])
user.save()
logger.info("Created new user %s" % user)
login(request, user)
messages.warning(request, _('Add an API key to set up your account.'))
return redirect("auth_dashboard")
else:
logger.error("Unable to register new user: username %s already exists." % form.cleaned_data['username'])
return render(request, 'public/register.html', context={'form': form, 'error': True})
else:
logger.debug("Registration form invalid. Returning for user %s to make corrections." % request.user)
else:
logger.debug("Returning blank registration form.")
form = RegistrationForm()
return render(request, 'public/register.html', context={'form': form})
def index_view(request):
logger.debug("index_view called by user %s" % request.user)
return render(request, 'public/index.html')
@login_required
def help_view(request):
logger.debug("help_view called by user %s" % request.user)
return render(request, 'registered/help.html')
@token_required(new=True)
def sso_login(request, token):
@token_required(scopes=settings.LOGIN_TOKEN_SCOPES)
def main_character_change(request, token):
logger.debug("main_character_change called by user %s for character %s" % (request.user, token.character_name))
try:
char = EveCharacter.objects.get(character_id=token.character_id)
if char.user:
if char.user.is_active:
login(request, char.user)
token.user = char.user
token.save()
return redirect('auth_dashboard')
else:
messages.error(request, _('Your account has been disabled.'))
else:
messages.warning(request,
_('Authenticated character has no owning account. Please log in with username and password.'))
except EveCharacter.DoesNotExist:
messages.error(request, _('No account exists with the authenticated character. Please create an account first.'))
return redirect(login_user)
co = CharacterOwnership.objects.get(character__character_id=token.character_id)
except CharacterOwnership.DoesNotExist:
co = CharacterOwnership.objects.create_by_token(token)
request.user.profile.main_character = co.character
request.user.profile.save(update_fields=['main_character'])
messages.success(request, _('Changed main character to %(char)s') % {"char": co.character})
return redirect("auth_dashboard")
@token_required(new=True, scopes=settings.LOGIN_TOKEN_SCOPES)
def add_character(request, token):
if CharacterOwnership.objects.filter(character__character_id=token.character_id).filter(
owner_hash=token.character_owner_hash).filter(user=request.user).exists():
messages.success(request, _('Added %(name)s to your account.'% ({'name': token.character_name})))
else:
messages.error(request, _('Failed to add %(name)s to your account.' % ({'name': token.charater_name})))
return redirect('authentication:dashboard')
"""
Override the HMAC two-step registration view to accommodate the three-step registration required.
Step 1: OAuth token to create user and profile.
Step 2: Get email and send activation link (but do not save email).
Step 3: Get link, save email and activate.
Step 1 is necessary to automatically assign character ownership and a main character, both of which require a saved User
model - this means the ensuing registration form cannot create the user because it already exists.
Email is not saved to the user model in Step 2 as a way of differentiating users who have not yet completed registration
(is_active=False) and users who have been disabled by an admin (is_active=False, email present).
Because of this, the email address needs to be assigned in Step 3 after clicking the link, which means the link must
have the email address embedded much like the username. Key creation and decoding is overridden to support this action.
"""
# Step 1
@token_required(new=True, scopes=settings.LOGIN_TOKEN_SCOPES)
def sso_login(request, token):
user = authenticate(token=token)
if user and user.is_active:
login(request, user)
return redirect(request.POST.get('next', request.GET.get('next', 'auth_dashboard')))
elif user and not user.email:
# Store the new user PK in the session to enable us to identify the registering user in Step 2
request.session['registration_uid'] = user.pk
# Go to Step 2
return redirect('authentication:register')
else:
messages.error(request, _('Unable to authenticate as the selected character.'))
return redirect(settings.LOGIN_URL)
# Step 2
class RegistrationView(BaseRegistrationView):
form_class = RegistrationForm
success_url = 'authentication:dashboard'
def dispatch(self, *args, **kwargs):
# We're storing a key in the session to pass user information from OAuth response. Make sure it's there.
if not self.request.session.get('registration_uid', None) or not User.objects.filter(
pk=self.request.session.get('registration_uid')).exists():
messages.error(self.request, _('Registration token has expired.'))
return redirect(settings.LOGIN_URL)
return super(RegistrationView, self).dispatch(*args, **kwargs)
def register(self, form):
user = User.objects.get(pk=self.request.session.get('registration_uid'))
user.email = form.cleaned_data['email']
user_registered.send(self.__class__, user=user, request=self.request)
# Go to Step 3
self.send_activation_email(user)
return user
def get_activation_key(self, user):
return signing.dumps(obj=[getattr(user, User.USERNAME_FIELD), user.email], salt=REGISTRATION_SALT)
# Step 3
class ActivationView(BaseActivationView):
def validate_key(self, activation_key):
try:
dump = signing.loads(activation_key, salt=REGISTRATION_SALT,
max_age=settings.ACCOUNT_ACTIVATION_DAYS * 86400)
return dump
except signing.BadSignature:
return None
def activate(self, *args, **kwargs):
dump = self.validate_key(kwargs.get('activation_key'))
if dump:
user = self.get_user(dump[0])
if user:
user.email = dump[1]
user.is_active = True
user.save()
return user
return False