Fix registration.

Fix state assignment.
Fix character ownership transfer.
Disable non-staff passwords.
Fix dashboard groups panel placement.
Fix corpstats viewmodel retrieval.
This commit is contained in:
Adarnof 2017-03-24 19:50:50 -04:00
parent ab10f062f7
commit 963cecb365
33 changed files with 268 additions and 317 deletions

View File

@ -4,5 +4,5 @@ from __future__ import absolute_import, unicode_literals
# Django starts so that shared_task will use this app. # Django starts so that shared_task will use this app.
from .celeryapp import app as celery_app # noqa from .celeryapp import app as celery_app # noqa
__version__ = '1.15.0' __version__ = '1.16-dev'
NAME = 'Alliance Auth v%s' % __version__ NAME = 'Alliance Auth v%s' % __version__

View File

@ -225,6 +225,7 @@ SITE_NAME = 'Alliance Auth'
################# #################
# EMAIL SETTINGS # EMAIL SETTINGS
################# #################
# DEFAULT_FROM_EMAIL - no-reply email address
# DOMAIN - The Alliance Auth domain (or subdomain) address, starting with http:// # DOMAIN - The Alliance Auth domain (or subdomain) address, starting with http://
# EMAIL_HOST - SMTP Server URL # EMAIL_HOST - SMTP Server URL
# EMAIL_PORT - SMTP Server PORT # EMAIL_PORT - SMTP Server PORT
@ -232,6 +233,7 @@ SITE_NAME = 'Alliance Auth'
# EMAIL_HOST_PASSWORD - Email Password # EMAIL_HOST_PASSWORD - Email Password
# EMAIL_USE_TLS - Set to use TLS encryption # EMAIL_USE_TLS - Set to use TLS encryption
################# #################
DEFAULT_FROM_EMAIL = 'no-reply@example.com'
DOMAIN = 'https://example.com' DOMAIN = 'https://example.com'
EMAIL_HOST = 'smtp.gmail.com' EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587 EMAIL_PORT = 587
@ -261,10 +263,12 @@ ESI_SSO_CALLBACK_URL = ''
# LOGIN_REDIRECT_URL - default destination when logging in if no redirect specified # LOGIN_REDIRECT_URL - default destination when logging in if no redirect specified
# LOGOUT_REDIRECT_URL - destination after logging out # LOGOUT_REDIRECT_URL - destination after logging out
# LOGIN_TOKEN_SCOPES - scopes required on new tokens when logging in. Cannot be blank. # LOGIN_TOKEN_SCOPES - scopes required on new tokens when logging in. Cannot be blank.
# ACCOUNT_ACTIVATION_DAYS - number of days email verification tokens are valid for
################## ##################
LOGIN_REDIRECT_URL = 'authentication:dashboard' LOGIN_REDIRECT_URL = 'authentication:dashboard'
LOGOUT_REDIRECT_URL = 'authentication:dashboard' LOGOUT_REDIRECT_URL = 'authentication:dashboard'
LOGIN_TOKEN_SCOPES = ['esi-characters.read_opportunities.v1'] LOGIN_TOKEN_SCOPES = ['esi-characters.read_opportunities.v1']
ACCOUNT_ACTIVATION_DAYS = 1
##################### #####################
# Alliance Market # Alliance Market

View File

@ -4,6 +4,7 @@ from django.conf.urls.i18n import i18n_patterns
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.contrib import admin from django.contrib import admin
import authentication.urls import authentication.urls
from authentication import hmac_urls
import authentication.views import authentication.views
import services.views import services.views
import groupmanagement.views import groupmanagement.views
@ -19,6 +20,7 @@ import esi.urls
import permissions_tool.urls import permissions_tool.urls
from alliance_auth import NAME from alliance_auth import NAME
from alliance_auth.hooks import get_hooks from alliance_auth.hooks import get_hooks
from django.views.generic.base import TemplateView
admin.site.site_header = NAME admin.site.site_header = NAME
@ -30,6 +32,8 @@ urlpatterns = [
# Authentication # Authentication
url(r'', include(authentication.urls, namespace='authentication')), url(r'', include(authentication.urls, namespace='authentication')),
url(r'^account/login/$', TemplateView.as_view(template_name='public/login.html'), name='auth_login_user'),
url(r'account/', include(hmac_urls)),
# Admin urls # Admin urls
url(r'^admin/', include(admin.site.urls)), url(r'^admin/', include(admin.site.urls)),

View File

@ -5,7 +5,7 @@ from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils.text import slugify from django.utils.text import slugify
from django import forms from django import forms
from authentication.models import State, get_guest_state from authentication.models import State, get_guest_state, CharacterOwnership, UserProfile
from alliance_auth.hooks import get_hooks from alliance_auth.hooks import get_hooks
from services.hooks import ServicesHook from services.hooks import ServicesHook
@ -97,3 +97,6 @@ class StateAdmin(admin.ModelAdmin):
if obj == get_guest_state(): if obj == get_guest_state():
return False return False
admin.site.register(CharacterOwnership)
admin.site.register(UserProfile)

View File

@ -45,7 +45,7 @@ class StateBackend(ModelBackend):
return self.create_user(token) return self.create_user(token)
def create_user(self, token): def create_user(self, token):
username = self.iterate_username(token.charater_name) # build unique username off character name username = self.iterate_username(token.character_name) # build unique username off character name
user = User.objects.create_user(username) user = User.objects.create_user(username)
user.set_unusable_password() # prevent login via password user.set_unusable_password() # prevent login via password
user.is_active = False # prevent login until email set user.is_active = False # prevent login until email set

View File

@ -0,0 +1,13 @@
from django.conf.urls import url, include
from authentication import views
urlpatterns = [
url(r'^activate/complete/$', views.activation_complete, name='registration_activation_complete'),
# The activation key can make use of any character from the
# URL-safe base64 alphabet, plus the colon as a separator.
url(r'^activate/(?P<activation_key>[-:\w]+)/$', views.ActivationView.as_view(), name='registration_activate'),
url(r'^register/$', views.RegistrationView.as_view(), name='registration_register'),
url(r'^register/complete/$', views.registration_complete, name='registration_complete'),
url(r'^register/closed/$', views.registration_closed, name='registration_disallowed'),
url(r'', include('registration.auth_urls')),
]

View File

@ -7,21 +7,25 @@ import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def available_states_query(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 query
class CharacterOwnershipManager(Manager): class CharacterOwnershipManager(Manager):
def create_by_token(self, token): def create_by_token(self, token):
if not EveCharacter.objects.filter(character_id=token.character_id).exists(): if not EveCharacter.objects.filter(character_id=token.character_id).exists():
EveManager.create_character(token.character_id) EveManager.create_character(token.character_id)
return self.create(character=EveCharacter.objects.get(characte_id=token.character_id), user=token.user, return self.create(character=EveCharacter.objects.get(character_id=token.character_id), user=token.user,
owner_hash=token.character_owner_hash) owner_hash=token.character_owner_hash)
class StateQuerySet(QuerySet): class StateQuerySet(QuerySet):
def available_to_character(self, character): def available_to_character(self, character):
query = Q(member_characters__character_id=character.character_id) return self.filter(available_states_query(character))
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): def available_to_user(self, user):
if user.profile.main_character: if user.profile.main_character:
@ -41,7 +45,7 @@ class StateManager(Manager):
return self.get_queryset().available_to_user(user) return self.get_queryset().available_to_user(user)
def get_for_character(self, character): def get_for_character(self, character):
states = self.get_queryset().available_to_character(character).order_by('priority') states = self.get_queryset().available_to_character(character)
if states.exists(): if states.exists():
return states[0] return states[0]
else: else:

View File

@ -6,6 +6,7 @@ import authentication.models
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
from django.contrib.auth.hashers import make_password
def create_guest_state(apps, schema_editor): def create_guest_state(apps, schema_editor):
@ -41,8 +42,8 @@ def create_member_group(apps, schema_editor):
State = apps.get_model('authentication', 'State') State = apps.get_model('authentication', 'State')
member_state_name = getattr(settings, 'DEFAULT_AUTH_GROUP', 'Member') member_state_name = getattr(settings, 'DEFAULT_AUTH_GROUP', 'Member')
g = Group.objects.get_or_create(name=member_state_name)[0]
try: try:
g = Group.objects.get(name=member_state_name)
# move permissions back # move permissions back
state = State.objects.get(name=member_state_name) state = State.objects.get(name=member_state_name)
[g.permissions.add(p.pk) for p in state.permissions.all()] [g.permissions.add(p.pk) for p in state.permissions.all()]
@ -50,7 +51,7 @@ def create_member_group(apps, schema_editor):
# move users back # move users back
for profile in state.userprofile_set.all().select_related('user'): for profile in state.userprofile_set.all().select_related('user'):
profile.user.groups.add(g.pk) profile.user.groups.add(g.pk)
except State.DoesNotExist: except (Group.DoesNotExist, State.DoesNotExist):
pass pass
@ -82,8 +83,8 @@ def create_blue_group(apps, schema_editor):
State = apps.get_model('authentication', 'State') State = apps.get_model('authentication', 'State')
blue_state_name = getattr(settings, 'DEFAULT_BLUE_GROUP', 'Blue') blue_state_name = getattr(settings, 'DEFAULT_BLUE_GROUP', 'Blue')
g = Group.objects.get_or_create(name=blue_state_name)[0]
try: try:
g = Group.objects.get(name=blue_state_name)
# move permissions back # move permissions back
state = State.objects.get(name=blue_state_name) state = State.objects.get(name=blue_state_name)
[g.permissions.add(p.pk) for p in state.permissions.all()] [g.permissions.add(p.pk) for p in state.permissions.all()]
@ -91,7 +92,7 @@ def create_blue_group(apps, schema_editor):
# move users back # move users back
for profile in state.userprofile_set.all().select_related('user'): for profile in state.userprofile_set.all().select_related('user'):
profile.user.groups.add(g.pk) profile.user.groups.add(g.pk)
except State.DoesNotExist: except (Group.DoesNotExist, State.DoesNotExist):
pass pass
@ -124,17 +125,18 @@ def create_profiles(apps, schema_editor):
unique_mains = [auth['main_char_id'] for auth in unique_mains = [auth['main_char_id'] for auth in
AuthServicesInfo.objects.exclude(main_char_id='').values('main_char_id').annotate( AuthServicesInfo.objects.exclude(main_char_id='').values('main_char_id').annotate(
n=models.Count('main_char_id')) if n=models.Count('main_char_id')) if
auth['n'] == 1 and EveCharacter.objects.filter(character_id=auth['main_char_id'].exists())] 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') auths = AuthServicesInfo.objects.filter(main_char_id__in=unique_mains).select_related('user')
for auth in auths: for auth in auths:
# carry states and mains forward # 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') state = State.objects.get(name=auth.state if auth.state else 'Guest')
profile.state = state.pk
char = EveCharacter.objects.get(character_id=auth.main_char_id) char = EveCharacter.objects.get(character_id=auth.main_char_id)
profile.main_character = char.pk profile = UserProfile.objects.create(user=auth.user, state=state, main_character=char)
profile.save() for auth in AuthServicesInfo.objects.exclude(main_char_id__in=unique_mains).select_related('user'):
# prepare empty profiles
state = State.objects.get(name='Guest')
UserProfile.objects.create(user=auth.user, state=state)
def recreate_authservicesinfo(apps, schema_editor): def recreate_authservicesinfo(apps, schema_editor):
@ -154,6 +156,15 @@ def recreate_authservicesinfo(apps, schema_editor):
AuthServicesInfo.objects.update_or_create(user=profile.user, defaults={'state': profile.state.name}) AuthServicesInfo.objects.update_or_create(user=profile.user, defaults={'state': profile.state.name})
def disable_passwords(apps, schema_editor):
User = apps.get_model('auth', 'User')
for u in User.objects.exclude(is_staff=True):
# remove passwords for non-staff users to prevent password-based authentication
# set_unusable_password is unavailable in migrations because :reasons:
u.password = make_password(None)
u.save()
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('auth', '0008_alter_user_username_max_length'), ('auth', '0008_alter_user_username_max_length'),
@ -187,13 +198,13 @@ class Migration(migrations.Migration):
('name', models.CharField(max_length=20, unique=True)), ('name', models.CharField(max_length=20, unique=True)),
('priority', models.IntegerField(unique=True)), ('priority', models.IntegerField(unique=True)),
('public', models.BooleanField(default=False)), ('public', models.BooleanField(default=False)),
('member_alliances', models.ManyToManyField(to='eveonline.EveAllianceInfo')), ('member_alliances', models.ManyToManyField(blank=True,to='eveonline.EveAllianceInfo')),
('member_characters', models.ManyToManyField(to='eveonline.EveCharacter')), ('member_characters', models.ManyToManyField(blank=True,to='eveonline.EveCharacter')),
('member_corporations', models.ManyToManyField(to='eveonline.EveCorporationInfo')), ('member_corporations', models.ManyToManyField(blank=True,to='eveonline.EveCorporationInfo')),
('permissions', models.ManyToManyField(blank=True, to='auth.Permission')), ('permissions', models.ManyToManyField(blank=True, to='auth.Permission')),
], ],
options={ options={
'ordering': ['priority'], 'ordering': ['-priority'],
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
@ -204,7 +215,7 @@ class Migration(migrations.Migration):
models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL,
to='eveonline.EveCharacter')), to='eveonline.EveCharacter')),
('state', models.ForeignKey(on_delete=models.SET(authentication.models.get_guest_state), ('state', models.ForeignKey(on_delete=models.SET(authentication.models.get_guest_state),
to='authentication.State')), to='authentication.State', default=authentication.models.get_guest_state_pk)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile',
to=settings.AUTH_USER_MODEL)), to=settings.AUTH_USER_MODEL)),
], ],
@ -224,4 +235,5 @@ class Migration(migrations.Migration):
migrations.DeleteModel( migrations.DeleteModel(
name='AuthServicesInfo', name='AuthServicesInfo',
), ),
migrations.RunPython(disable_passwords, migrations.RunPython.noop),
] ]

View File

@ -4,6 +4,9 @@ from django.db import models
from django.contrib.auth.models import User, Permission from django.contrib.auth.models import User, Permission
from authentication.managers import CharacterOwnershipManager, StateManager from authentication.managers import CharacterOwnershipManager, StateManager
from eveonline.models import EveCharacter, EveCorporationInfo, EveAllianceInfo from eveonline.models import EveCharacter, EveCorporationInfo, EveAllianceInfo
import logging
logger = logging.getLogger(__name__)
@python_2_unicode_compatible @python_2_unicode_compatible
@ -12,22 +15,35 @@ class State(models.Model):
permissions = models.ManyToManyField(Permission, blank=True) permissions = models.ManyToManyField(Permission, blank=True)
priority = models.IntegerField(unique=True) priority = models.IntegerField(unique=True)
member_characters = models.ManyToManyField(EveCharacter) member_characters = models.ManyToManyField(EveCharacter, blank=True)
member_corporations = models.ManyToManyField(EveCorporationInfo) member_corporations = models.ManyToManyField(EveCorporationInfo, blank=True)
member_alliances = models.ManyToManyField(EveAllianceInfo) member_alliances = models.ManyToManyField(EveAllianceInfo, blank=True)
public = models.BooleanField(default=False) public = models.BooleanField(default=False)
objects = StateManager() objects = StateManager()
class Meta: class Meta:
ordering = ['priority'] ordering = ['-priority']
def __str__(self): def __str__(self):
return self.name return self.name
def available_to_character(self, character):
return self in State.objects.available_to_character(character)
def available_to_user(self, user):
return self in State.objects.available_to_user(user)
def get_guest_state(): def get_guest_state():
return State.objects.update_or_create(name='Guest', defaults={'priority': 0, 'public': True})[0] try:
return State.objects.get(name='Guest')
except State.DoesNotExist:
return State.objects.create(name='Guest', priority=0, public=True)
def get_guest_state_pk():
return get_guest_state().pk
@python_2_unicode_compatible @python_2_unicode_compatible
@ -37,15 +53,18 @@ class UserProfile(models.Model):
user = models.OneToOneField(User, related_name='profile', on_delete=models.CASCADE) user = models.OneToOneField(User, related_name='profile', on_delete=models.CASCADE)
main_character = models.OneToOneField(EveCharacter, blank=True, null=True, on_delete=models.SET_NULL) 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)) state = models.ForeignKey(State, on_delete=models.SET(get_guest_state), default=get_guest_state_pk)
def assign_state(self, commit=True): def assign_state(self, commit=True):
self.state = State.objects.get_for_user(self.user) state = State.objects.get_for_user(self.user)
if commit: if self.state != state:
self.save(update_fields=['state']) self.state = state
if commit:
logger.info('Updating {} state to {}'.format(self.user, self.state))
self.save(update_fields=['state'])
def __str__(self): def __str__(self):
return "%s Profile" % self.user return str(self.user)
@python_2_unicode_compatible @python_2_unicode_compatible

View File

@ -1,8 +1,9 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db.models.signals import post_save, pre_delete from django.db.models.signals import post_save, pre_delete, m2m_changed
from django.db.models import Q
from django.dispatch import receiver from django.dispatch import receiver
from django.contrib.auth.models import User from django.contrib.auth.models import User
from authentication.models import CharacterOwnership, UserProfile, get_guest_state from authentication.models import CharacterOwnership, UserProfile, get_guest_state, State
from services.tasks import validate_services from services.tasks import validate_services
from esi.models import Token from esi.models import Token
from eveonline.managers import EveManager from eveonline.managers import EveManager
@ -12,12 +13,48 @@ import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def trigger_state_check(state):
# evaluate all current members to ensure they still have access
for profile in state.userprofile_set.all():
profile.assign_state()
# we may now be available to others with lower states
check_states = State.objects.filter(priority__lt=state.priority)
for profile in UserProfile.objects.filter(state__in=check_states):
if state.available_to_user(profile.user):
profile.state = state
profile.save(update_fields=['state'])
@receiver(m2m_changed, sender=State.member_characters.through)
def state_member_characters_changed(sender, instance, action, *args, **kwargs):
if action.startswith('post_'):
trigger_state_check(instance)
@receiver(m2m_changed, sender=State.member_corporations.through)
def state_member_corporations_changed(sender, instance, action, *args, **kwargs):
if action.startswith('post_'):
trigger_state_check(instance)
@receiver(m2m_changed, sender=State.member_alliances.through)
def state_member_alliances_changed(sender, instance, action, *args, **kwargs):
if action.startswith('post_'):
trigger_state_check(instance)
@receiver(post_save, sender=State)
def state_saved(sender, instance, *args, **kwargs):
trigger_state_check(instance)
# Is there a smarter way to intercept pre_save with a diff main_character or state? # Is there a smarter way to intercept pre_save with a diff main_character or state?
@receiver(post_save, sender=UserProfile) @receiver(post_save, sender=UserProfile)
def reassess_on_profile_save(sender, instance, created, *args, **kwargs): def reassess_on_profile_save(sender, instance, created, *args, **kwargs):
# catches post_save from profiles to trigger necessary service and state checks # catches post_save from profiles to trigger necessary service and state checks
if not created: if not created:
update_fields = kwargs.pop('update_fields', []) update_fields = kwargs.pop('update_fields', []) or []
if 'state' not in update_fields: if 'state' not in update_fields:
instance.assign_state() instance.assign_state()
# TODO: how do we prevent running this twice on profile state change? # TODO: how do we prevent running this twice on profile state change?
@ -35,14 +72,17 @@ def create_required_models(sender, instance, created, *args, **kwargs):
def record_character_ownership(sender, instance, created, *args, **kwargs): def record_character_ownership(sender, instance, created, *args, **kwargs):
if created: if created:
# purge ownership records if the hash or auth user account has changed # purge ownership records if the hash or auth user account has changed
CharacterOwnership.objects.filter(character__character_id=instance.character_id).exclude( CharacterOwnership.objects.filter(character__character_id=instance.character_id).exclude(Q(
owner_hash=instance.owner_hash).exclude(user=instance.user).delete() owner_hash=instance.character_owner_hash) & Q(user=instance.user)).delete()
# create character if needed # create character if needed
if EveCharacter.objects.filter(character_id=instance.character_id).exists() is False: if EveCharacter.objects.filter(character_id=instance.character_id).exists() is False:
EveManager.create_character(instance.character_id) EveManager.create_character(instance.character_id)
char = EveCharacter.objects.get(character_id=instance.character_id) char = EveCharacter.objects.get(character_id=instance.character_id)
CharacterOwnership.objects.update_or_create(character=char, # check if we need to create ownership
defaults={'owner_hash': instance.owner_hash, 'user': instance.user}) if instance.user and not CharacterOwnership.objects.filter(character__character_id=instance.character_id).exists():
CharacterOwnership.objects.update_or_create(character=char,
defaults={'owner_hash': instance.character_owner_hash,
'user': instance.user})
@receiver(pre_delete, sender=CharacterOwnership) @receiver(pre_delete, sender=CharacterOwnership)

View File

@ -49,23 +49,23 @@
</div> </div>
</div> </div>
</div> </div>
</div> <div class="col-lg-6 text-center">
<div class="col-lg-6 text-center"> <div class="panel panel-success">
<div class="panel panel-success"> <div class="panel-heading">{% trans "Groups" %}</div>
<div class="panel-heading">{% trans "Groups" %}</div> <div class="panel-body">
<div class="panel-body"> <div style="height: 236px;overflow:-moz-scrollbars-vertical;overflow-y:auto;">
<div style="height: 236px;overflow:-moz-scrollbars-vertical;overflow-y:auto;"> <table class="table table-striped">
<table class="table table-striped"> {% for group in user.groups.all %}
{% for group in user.groups.all %} <tr>
<tr> <td>{{ group.name }}</td>
<td>{{ group.name }}</td> </tr>
</tr> {% endfor %}
{% endfor %} </table>
</table> </div>
</div> </div>
</div> </div>
</div> </div>
</div> <div>
</div> </div>
<div class="clearfix"></div> <div class="clearfix"></div>
<div class="panel panel-default"> <div class="panel panel-default">
@ -81,6 +81,7 @@
{% for ownership in request.user.character_ownerships.all %} {% for ownership in request.user.character_ownerships.all %}
{% with ownership.character as char %} {% with ownership.character as char %}
<tr> <tr>
<td class="text-center"><img class="ra-avatar img-circle" src="https://image.eveonline.com/Character/{{ char.character_id }}_32.jpg"></td>
<td class="text-center">{{ char.character_name }}</td> <td class="text-center">{{ char.character_name }}</td>
<td class="text-center">{{ char.corporation_name }}</td> <td class="text-center">{{ char.corporation_name }}</td>
<td class="text-center">{{ char.alliance_name }}</td> <td class="text-center">{{ char.alliance_name }}</td>

View File

@ -1,3 +1,4 @@
{% load static %}
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">

View File

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

View File

@ -67,9 +67,9 @@
{% if user.is_staff %} {% if user.is_staff %}
<li><a href="{% url 'admin:index' %}">{% trans "Admin" %}</a></li> <li><a href="{% url 'admin:index' %}">{% trans "Admin" %}</a></li>
{% endif %} {% endif %}
<li><a href="{% url 'auth_logout_user' %}">{% trans "Logout" %}</a></li> <li><a href="{% url 'auth_logout' %}">{% trans "Logout" %}</a></li>
{% else %} {% else %}
<li><a href="{% url 'auth_login_user' %}">{% trans "Login" %}</a></li> <li><a href="{% url 'authentication:login' %}">{% trans "Login" %}</a></li>
{% endif %} {% endif %}
</ul> </ul>
@ -85,7 +85,7 @@
</li> </li>
<li> <li>
<a class="{% navactive request 'auth_dashboard' %}" href="{% url 'auth_dashboard' %}"> <a class="{% navactive request 'authentication:dashboard' %}" href="{% url 'authentication:dashboard' %}">
<i class="fa fa-dashboard fa-fw grayiconecolor"></i>{% trans " Dashboard" %} <i class="fa fa-dashboard fa-fw grayiconecolor"></i>{% trans " Dashboard" %}
</a> </a>
</li> </li>
@ -95,7 +95,7 @@
</a> </a>
</li> </li>
<li> <li>
<a class="{% navactive request 'auth_help' %}" href="{% url 'auth_help' %}"> <a class="{% navactive request 'authentication:help' %}" href="{% url 'authentication:help' %}">
<i class="fa fa-question fa-fw grayiconecolor"></i>{% trans " Help" %} <i class="fa fa-question fa-fw grayiconecolor"></i>{% trans " Help" %}
</a> </a>
</li> </li>
@ -170,9 +170,6 @@
</a> </a>
</li> </li>
{% menu_aux %} {% 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 %} {% if perms.auth.jabber_broadcast or perms.auth.jabber_broadcast_all or user.is_superuser %}
<li> <li>

View File

@ -0,0 +1,5 @@
{% extends 'public/middle_box.html' %}
{% load i18n %}
{% block middle_box_content %}
<div class="alert alert-danger">{% trans 'Invalid or expired activation link.' %}</div>
{% endblock %}

View File

@ -0,0 +1,9 @@
You're receiving this email because someone has entered this email address while registering for an account on {{ site.domain }}
If this was you, please go to the following URL to confirm your email address:
{{ url }}
This link will expire in {{ expiration_days }} day{{ plural }}.
If this was not you, it is safe to ignore this email.

View File

@ -0,0 +1 @@
Confirm your Alliance Auth account email address

View File

@ -1,24 +0,0 @@
{% 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

@ -1,26 +0,0 @@
{% 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

@ -1,44 +0,0 @@
{% 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

@ -1,22 +0,0 @@
{% 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

@ -1,32 +0,0 @@
{% 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

@ -1,23 +0,0 @@
{% 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

@ -1,27 +0,0 @@
{% 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

@ -0,0 +1,14 @@
{% extends 'public/middle_box.html' %}
{% load bootstrap %}
{% load i18n %}
{% load static %}
{% block title %}Register{% endblock %}
{% block middle_box_content %}
<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 "Submit" %}</button>
<br/>
</form>
{% endblock %}

View File

@ -1,20 +1,15 @@
from django.conf.urls import url, include from django.conf.urls import url
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.views.generic.base import TemplateView from django.views.generic.base import TemplateView
from authentication import views 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' app_name = 'authentication'
urlpatterns = [ urlpatterns = [
url(r'^$', TemplateView.as_view(template_name='public/login.html'), name='login'), url(r'^$', login_required(TemplateView.as_view(template_name='authentication/dashboard.html')),),
url(r'^account/login/$', TemplateView.as_view(template_name='public/login.html')), url(r'^account/login/$', TemplateView.as_view(template_name='public/login.html'), name='login'),
url(r'^account/characters/main/$', views.main_character_change, name='change_main_character'), 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/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'^help/$', login_required(TemplateView.as_view(template_name='public/help.html')), name='help'),
url(r'^dashboard/$', url(r'^dashboard/$',
login_required(TemplateView.as_view(template_name='authentication/dashboard.html')), name='dashboard'), login_required(TemplateView.as_view(template_name='authentication/dashboard.html')), name='dashboard'),

View File

@ -9,6 +9,7 @@ from django.contrib import messages
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.conf import settings from django.conf import settings
from django.core import signing from django.core import signing
from django.core.urlresolvers import reverse
from esi.decorators import token_required from esi.decorators import token_required
from registration.backends.hmac.views import RegistrationView as BaseRegistrationView, \ from registration.backends.hmac.views import RegistrationView as BaseRegistrationView, \
ActivationView as BaseActivationView, REGISTRATION_SALT ActivationView as BaseActivationView, REGISTRATION_SALT
@ -23,13 +24,19 @@ logger = logging.getLogger(__name__)
def main_character_change(request, token): def main_character_change(request, token):
logger.debug("main_character_change called by user %s for character %s" % (request.user, token.character_name)) logger.debug("main_character_change called by user %s for character %s" % (request.user, token.character_name))
try: try:
co = CharacterOwnership.objects.get(character__character_id=token.character_id) co = CharacterOwnership.objects.get(character__character_id=token.character_id, user=request.user)
except CharacterOwnership.DoesNotExist: except CharacterOwnership.DoesNotExist:
co = CharacterOwnership.objects.create_by_token(token) if not CharacterOwnership.objects.filter(character__character_id=token.character_id).exists():
request.user.profile.main_character = co.character co = CharacterOwnership.objects.create_by_token(token)
request.user.profile.save(update_fields=['main_character']) else:
messages.success(request, _('Changed main character to %(char)s') % {"char": co.character}) messages.error(request, 'Cannot change main character to %(char)s: character owned by a different account.' % ({'char': token.character_name}))
return redirect("auth_dashboard") co = None
if co:
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})
logger.info('Changed user %(user)s main character to %(char)s' % ({'user': request.user, 'char': co.character}))
return redirect("authentication:dashboard")
@token_required(new=True, scopes=settings.LOGIN_TOKEN_SCOPES) @token_required(new=True, scopes=settings.LOGIN_TOKEN_SCOPES)
@ -38,7 +45,7 @@ def add_character(request, token):
owner_hash=token.character_owner_hash).filter(user=request.user).exists(): owner_hash=token.character_owner_hash).filter(user=request.user).exists():
messages.success(request, _('Added %(name)s to your account.'% ({'name': token.character_name}))) messages.success(request, _('Added %(name)s to your account.'% ({'name': token.character_name})))
else: else:
messages.error(request, _('Failed to add %(name)s to your account.' % ({'name': token.charater_name}))) messages.error(request, _('Failed to add %(name)s to your account: they already have an account.' % ({'name': token.character_name})))
return redirect('authentication:dashboard') return redirect('authentication:dashboard')
@ -65,12 +72,12 @@ def sso_login(request, token):
user = authenticate(token=token) user = authenticate(token=token)
if user and user.is_active: if user and user.is_active:
login(request, user) login(request, user)
return redirect(request.POST.get('next', request.GET.get('next', 'auth_dashboard'))) return redirect(request.POST.get('next', request.GET.get('next', 'authentication:dashboard')))
elif user and not user.email: 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 # 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 request.session['registration_uid'] = user.pk
# Go to Step 2 # Go to Step 2
return redirect('authentication:register') return redirect('registration_register')
else: else:
messages.error(request, _('Unable to authenticate as the selected character.')) messages.error(request, _('Unable to authenticate as the selected character.'))
return redirect(settings.LOGIN_URL) return redirect(settings.LOGIN_URL)
@ -100,6 +107,13 @@ class RegistrationView(BaseRegistrationView):
def get_activation_key(self, user): def get_activation_key(self, user):
return signing.dumps(obj=[getattr(user, User.USERNAME_FIELD), user.email], salt=REGISTRATION_SALT) return signing.dumps(obj=[getattr(user, User.USERNAME_FIELD), user.email], salt=REGISTRATION_SALT)
def get_email_context(self, activation_key):
context = super(RegistrationView, self).get_email_context(activation_key)
context['url'] = context['site'].domain + reverse('registration_activate', args=[activation_key])
context['plural'] = 's' if context['expiration_days'] > 1 else '',
print(context)
return context
# Step 3 # Step 3
class ActivationView(BaseActivationView): class ActivationView(BaseActivationView):
@ -121,3 +135,18 @@ class ActivationView(BaseActivationView):
user.save() user.save()
return user return user
return False return False
def registration_complete(request):
messages.success(request, _('Sent confirmation email. Please follow the link to confirm your email address.'))
return redirect('authentication:login')
def activation_complete(request):
messages.success(request, _('Confirmed your email address. Please login to continue.'))
return redirect('authentication:dashboard')
def registration_closed(request):
messages.error(request, _('Registraion of new accounts it not allowed at this time.'))
return redirect('authentication:login')

View File

@ -145,7 +145,7 @@ class CorpStats(models.Model):
class ViewModel(object): class ViewModel(object):
def __init__(self, corpstats, user): def __init__(self, corpstats, user):
self.corp = corpstats.corp self.corp = corpstats.corp
self.members = corpstats.get_member_objects(user) self.members = corpstats.get_member_objects()
self.can_update = corpstats.can_update(user) self.can_update = corpstats.can_update(user)
self.total_members = len(self.members) self.total_members = len(self.members)
self.total_users = corpstats.user_count(self.members) self.total_users = corpstats.user_count(self.members)

View File

@ -90,7 +90,7 @@ def corpstats_view(request, corp_id=None):
members = [] members = []
if corpstats: if corpstats:
page = request.GET.get('page', 1) page = request.GET.get('page', 1)
members = get_page(corpstats.get_member_objects(request.user), page) members = get_page(corpstats.get_member_objects(), page)
if corpstats: if corpstats:
context.update({ context.update({

View File

@ -7,20 +7,14 @@ def auth_settings(request):
return { return {
'DOMAIN': settings.DOMAIN, 'DOMAIN': settings.DOMAIN,
'MUMBLE_URL': settings.MUMBLE_URL, 'MUMBLE_URL': settings.MUMBLE_URL,
'FORUM_URL': settings.FORUM_URL, 'FORUM_URL': settings.PHPBB3_URL,
'TEAMSPEAK3_PUBLIC_URL': settings.TEAMSPEAK3_PUBLIC_URL, 'TEAMSPEAK3_PUBLIC_URL': settings.TEAMSPEAK3_PUBLIC_URL,
'DISCORD_SERVER_ID': settings.DISCORD_GUILD_ID, 'DISCORD_SERVER_ID': settings.DISCORD_GUILD_ID,
'KILLBOARD_URL': settings.KILLBOARD_URL,
'DISCOURSE_URL': settings.DISCOURSE_URL, 'DISCOURSE_URL': settings.DISCOURSE_URL,
'IPS4_URL': settings.IPS4_URL, 'IPS4_URL': settings.IPS4_URL,
'SMF_URL': settings.SMF_URL, 'SMF_URL': settings.SMF_URL,
'MARKET_URL': settings.MARKET_URL, 'MARKET_URL': settings.MARKET_URL,
'EXTERNAL_MEDIA_URL': settings.EXTERNAL_MEDIA_URL,
'CURRENT_UTC_TIME': timezone.now(), 'CURRENT_UTC_TIME': timezone.now(),
'BLUE_API_MASK': settings.BLUE_API_MASK,
'BLUE_API_ACCOUNT': settings.BLUE_API_ACCOUNT,
'MEMBER_API_MASK': settings.MEMBER_API_MASK,
'MEMBER_API_ACCOUNT': settings.MEMBER_API_ACCOUNT,
'JABBER_URL': settings.JABBER_URL, 'JABBER_URL': settings.JABBER_URL,
'SITE_NAME': settings.SITE_NAME, 'SITE_NAME': settings.SITE_NAME,
} }

View File

@ -41,7 +41,7 @@ def only_one(function=None, key="", timeout=None):
@app.task(bind=True) @app.task(bind=True)
def validate_services(self, user): def validate_services(self, user):
logger.debug('Ensuring user %s has permissions for active services'.format(user)) logger.debug('Ensuring user {} has permissions for active services'.format(user))
# Iterate through services hooks and have them check the validity of the user # Iterate through services hooks and have them check the validity of the user
for svc in ServicesHook.get_services(): for svc in ServicesHook.get_services():
try: try:

View File

@ -1,12 +1,9 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.contrib import messages
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.shortcuts import render from django.shortcuts import render
from django.utils.translation import ugettext_lazy as _
from alliance_auth.hooks import get_hooks from alliance_auth.hooks import get_hooks
from eveonline.models import EveCharacter
from services.forms import FleetFormatterForm from services.forms import FleetFormatterForm
import logging import logging
@ -47,7 +44,7 @@ def fleet_formatter_view(request):
@login_required @login_required
def services_view(request): def services_view(request):
logger.debug("services_view called by user %s" % request.user) logger.debug("services_view called by user %s" % request.user)
char = request.profile.main_character char = request.user.profile.main_character
context = {'service_ctrls': []} context = {'service_ctrls': []}
for fn in get_hooks('services_hook'): for fn in get_hooks('services_hook'):
# Render hooked services controls # Render hooked services controls