diff --git a/alliance_auth/__init__.py b/alliance_auth/__init__.py index 4fbf5773..50fe2be2 100644 --- a/alliance_auth/__init__.py +++ b/alliance_auth/__init__.py @@ -4,5 +4,5 @@ from __future__ import absolute_import, unicode_literals # Django starts so that shared_task will use this app. from .celeryapp import app as celery_app # noqa -__version__ = '1.15.0' +__version__ = '1.16-dev' NAME = 'Alliance Auth v%s' % __version__ diff --git a/alliance_auth/settings.py.example b/alliance_auth/settings.py.example index a6a7e6a8..38c19c58 100644 --- a/alliance_auth/settings.py.example +++ b/alliance_auth/settings.py.example @@ -225,6 +225,7 @@ SITE_NAME = 'Alliance Auth' ################# # EMAIL SETTINGS ################# +# DEFAULT_FROM_EMAIL - no-reply email address # DOMAIN - The Alliance Auth domain (or subdomain) address, starting with http:// # EMAIL_HOST - SMTP Server URL # EMAIL_PORT - SMTP Server PORT @@ -232,6 +233,7 @@ SITE_NAME = 'Alliance Auth' # EMAIL_HOST_PASSWORD - Email Password # EMAIL_USE_TLS - Set to use TLS encryption ################# +DEFAULT_FROM_EMAIL = 'no-reply@example.com' DOMAIN = 'https://example.com' EMAIL_HOST = 'smtp.gmail.com' EMAIL_PORT = 587 @@ -261,10 +263,12 @@ ESI_SSO_CALLBACK_URL = '' # LOGIN_REDIRECT_URL - default destination when logging in if no redirect specified # LOGOUT_REDIRECT_URL - destination after logging out # 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' LOGOUT_REDIRECT_URL = 'authentication:dashboard' LOGIN_TOKEN_SCOPES = ['esi-characters.read_opportunities.v1'] +ACCOUNT_ACTIVATION_DAYS = 1 ##################### # Alliance Market diff --git a/alliance_auth/urls.py b/alliance_auth/urls.py index 8961932d..d1a3fab5 100755 --- a/alliance_auth/urls.py +++ b/alliance_auth/urls.py @@ -4,6 +4,7 @@ from django.conf.urls.i18n import i18n_patterns from django.utils.translation import ugettext_lazy as _ from django.contrib import admin import authentication.urls +from authentication import hmac_urls import authentication.views import services.views import groupmanagement.views @@ -19,6 +20,7 @@ import esi.urls import permissions_tool.urls from alliance_auth import NAME from alliance_auth.hooks import get_hooks +from django.views.generic.base import TemplateView admin.site.site_header = NAME @@ -30,6 +32,8 @@ urlpatterns = [ # 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 url(r'^admin/', include(admin.site.urls)), diff --git a/authentication/admin.py b/authentication/admin.py index 65b6f793..de0acd74 100644 --- a/authentication/admin.py +++ b/authentication/admin.py @@ -5,7 +5,7 @@ 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 State, get_guest_state +from authentication.models import State, get_guest_state, CharacterOwnership, UserProfile from alliance_auth.hooks import get_hooks from services.hooks import ServicesHook @@ -97,3 +97,6 @@ class StateAdmin(admin.ModelAdmin): if obj == get_guest_state(): return False + +admin.site.register(CharacterOwnership) +admin.site.register(UserProfile) diff --git a/authentication/backends.py b/authentication/backends.py index 0fffd9e1..3812f532 100644 --- a/authentication/backends.py +++ b/authentication/backends.py @@ -45,7 +45,7 @@ class StateBackend(ModelBackend): return self.create_user(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.set_unusable_password() # prevent login via password user.is_active = False # prevent login until email set diff --git a/authentication/hmac_urls.py b/authentication/hmac_urls.py new file mode 100644 index 00000000..0f032390 --- /dev/null +++ b/authentication/hmac_urls.py @@ -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[-:\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')), +] \ No newline at end of file diff --git a/authentication/managers.py b/authentication/managers.py index 16c888c7..6e1f1d99 100755 --- a/authentication/managers.py +++ b/authentication/managers.py @@ -7,21 +7,25 @@ import logging 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): 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, + return self.create(character=EveCharacter.objects.get(character_id=token.character_id), user=token.user, owner_hash=token.character_owner_hash) 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) + return self.filter(available_states_query(character)) def available_to_user(self, user): if user.profile.main_character: @@ -41,7 +45,7 @@ class StateManager(Manager): return self.get_queryset().available_to_user(user) 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(): return states[0] else: diff --git a/authentication/migrations/0015_user_profiles.py b/authentication/migrations/0015_user_profiles.py index ebf40a88..8a858749 100644 --- a/authentication/migrations/0015_user_profiles.py +++ b/authentication/migrations/0015_user_profiles.py @@ -6,6 +6,7 @@ import authentication.models from django.conf import settings from django.db import migrations, models import django.db.models.deletion +from django.contrib.auth.hashers import make_password def create_guest_state(apps, schema_editor): @@ -41,8 +42,8 @@ def create_member_group(apps, schema_editor): 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: + g = Group.objects.get(name=member_state_name) # move permissions back state = State.objects.get(name=member_state_name) [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 for profile in state.userprofile_set.all().select_related('user'): profile.user.groups.add(g.pk) - except State.DoesNotExist: + except (Group.DoesNotExist, State.DoesNotExist): pass @@ -82,8 +83,8 @@ def create_blue_group(apps, schema_editor): 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: + g = Group.objects.get(name=blue_state_name) # move permissions back state = State.objects.get(name=blue_state_name) [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 for profile in state.userprofile_set.all().select_related('user'): profile.user.groups.add(g.pk) - except State.DoesNotExist: + except (Group.DoesNotExist, State.DoesNotExist): pass @@ -124,17 +125,18 @@ def create_profiles(apps, schema_editor): 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())] + 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.pk char = EveCharacter.objects.get(character_id=auth.main_char_id) - profile.main_character = char.pk - profile.save() + profile = UserProfile.objects.create(user=auth.user, state=state, main_character=char) + 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): @@ -154,6 +156,15 @@ def recreate_authservicesinfo(apps, schema_editor): 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): dependencies = [ ('auth', '0008_alter_user_username_max_length'), @@ -187,13 +198,13 @@ class Migration(migrations.Migration): ('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')), + ('member_alliances', models.ManyToManyField(blank=True,to='eveonline.EveAllianceInfo')), + ('member_characters', models.ManyToManyField(blank=True,to='eveonline.EveCharacter')), + ('member_corporations', models.ManyToManyField(blank=True,to='eveonline.EveCorporationInfo')), ('permissions', models.ManyToManyField(blank=True, to='auth.Permission')), ], options={ - 'ordering': ['priority'], + 'ordering': ['-priority'], }, ), migrations.CreateModel( @@ -204,7 +215,7 @@ class Migration(migrations.Migration): 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')), + to='authentication.State', default=authentication.models.get_guest_state_pk)), ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL)), ], @@ -224,4 +235,5 @@ class Migration(migrations.Migration): migrations.DeleteModel( name='AuthServicesInfo', ), + migrations.RunPython(disable_passwords, migrations.RunPython.noop), ] diff --git a/authentication/models.py b/authentication/models.py index d5d904c6..fa98c1ac 100755 --- a/authentication/models.py +++ b/authentication/models.py @@ -4,6 +4,9 @@ from django.db import models from django.contrib.auth.models import User, Permission from authentication.managers import CharacterOwnershipManager, StateManager from eveonline.models import EveCharacter, EveCorporationInfo, EveAllianceInfo +import logging + +logger = logging.getLogger(__name__) @python_2_unicode_compatible @@ -12,22 +15,35 @@ class State(models.Model): 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) + member_characters = models.ManyToManyField(EveCharacter, blank=True) + member_corporations = models.ManyToManyField(EveCorporationInfo, blank=True) + member_alliances = models.ManyToManyField(EveAllianceInfo, blank=True) public = models.BooleanField(default=False) objects = StateManager() class Meta: - ordering = ['priority'] + ordering = ['-priority'] def __str__(self): 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(): - 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 @@ -37,15 +53,18 @@ class UserProfile(models.Model): 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) - 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): - self.state = State.objects.get_for_user(self.user) - if commit: - self.save(update_fields=['state']) + state = State.objects.get_for_user(self.user) + if self.state != state: + self.state = state + if commit: + logger.info('Updating {} state to {}'.format(self.user, self.state)) + self.save(update_fields=['state']) def __str__(self): - return "%s Profile" % self.user + return str(self.user) @python_2_unicode_compatible diff --git a/authentication/signals.py b/authentication/signals.py index 10f9163d..e4ffe373 100644 --- a/authentication/signals.py +++ b/authentication/signals.py @@ -1,8 +1,9 @@ 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.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 esi.models import Token from eveonline.managers import EveManager @@ -12,12 +13,48 @@ import logging 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? @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', []) + update_fields = kwargs.pop('update_fields', []) or [] if 'state' not in update_fields: instance.assign_state() # 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): 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() + CharacterOwnership.objects.filter(character__character_id=instance.character_id).exclude(Q( + owner_hash=instance.character_owner_hash) & Q(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}) + # check if we need to create ownership + 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) diff --git a/authentication/templates/authentication/dashboard.html b/authentication/templates/authentication/dashboard.html index 1f291f2e..0cb36f7a 100644 --- a/authentication/templates/authentication/dashboard.html +++ b/authentication/templates/authentication/dashboard.html @@ -49,23 +49,23 @@ - -
-
-
{% trans "Groups" %}
-
-
- - {% for group in user.groups.all %} - - - - {% endfor %} -
{{ group.name }}
+
+
+
{% trans "Groups" %}
+
+
+ + {% for group in user.groups.all %} + + + + {% endfor %} +
{{ group.name }}
+
-
+
@@ -81,6 +81,7 @@ {% for ownership in request.user.character_ownerships.all %} {% with ownership.character as char %} + {{ char.character_name }} {{ char.corporation_name }} {{ char.alliance_name }} diff --git a/authentication/templates/public/base.html b/authentication/templates/public/base.html index 95e0525e..7675938b 100644 --- a/authentication/templates/public/base.html +++ b/authentication/templates/public/base.html @@ -1,3 +1,4 @@ +{% load static %} diff --git a/authentication/templates/public/login.html b/authentication/templates/public/login.html index e110d580..3d8b3164 100644 --- a/authentication/templates/public/login.html +++ b/authentication/templates/public/login.html @@ -1,27 +1,10 @@ -{% extends 'public/base.html' %} +{% extends 'public/middle_box.html' %} +{% load static %} {% block title %}Login{% endblock %} -{% block content %} -
- {% if messages %} - {% for message in messages %} -
{{ message }}
- {% endfor %} - {% endif %} -
-
-
-

- - - -

-
-
-
- {% include 'public/lang_select.html' %} -
-{% endblock %} -{% block extra_include %} - {% include 'bundles/bootstrap-js.html' %} -{% endblock %} - +{% block middle_box_content %} +

+ + + +

+{% endblock %} \ No newline at end of file diff --git a/authentication/templates/public/middle_box.html b/authentication/templates/public/middle_box.html new file mode 100644 index 00000000..ab478cbc --- /dev/null +++ b/authentication/templates/public/middle_box.html @@ -0,0 +1,24 @@ +{% extends 'public/base.html' %} +{% load static %} +{% block title %}Login{% endblock %} +{% block content %} +
+ {% if messages %} + {% for message in messages %} +
{{ message }}
+ {% endfor %} + {% endif %} +
+
+
+ {% block middle_box_content %} + {% endblock %} +
+
+ {% include 'public/lang_select.html' %} +
+
+{% endblock %} +{% block extra_include %} + {% include 'bundles/bootstrap-js.html' %} +{% endblock %} \ No newline at end of file diff --git a/authentication/templates/registered/base.html b/authentication/templates/registered/base.html index a5acd44d..fc351abd 100644 --- a/authentication/templates/registered/base.html +++ b/authentication/templates/registered/base.html @@ -67,9 +67,9 @@ {% if user.is_staff %}
  • {% trans "Admin" %}
  • {% endif %} -
  • {% trans "Logout" %}
  • +
  • {% trans "Logout" %}
  • {% else %} -
  • {% trans "Login" %}
  • +
  • {% trans "Login" %}
  • {% endif %} @@ -85,7 +85,7 @@
  • - + {% trans " Dashboard" %}
  • @@ -95,7 +95,7 @@
  • - + {% trans " Help" %}
  • @@ -170,9 +170,6 @@ {% menu_aux %} -
  • -
    {% trans "Util" %}
    -
  • {% if perms.auth.jabber_broadcast or perms.auth.jabber_broadcast_all or user.is_superuser %}
  • diff --git a/authentication/templates/registration/activate.html b/authentication/templates/registration/activate.html new file mode 100644 index 00000000..c5635df5 --- /dev/null +++ b/authentication/templates/registration/activate.html @@ -0,0 +1,5 @@ +{% extends 'public/middle_box.html' %} +{% load i18n %} +{% block middle_box_content %} +
    {% trans 'Invalid or expired activation link.' %}
    +{% endblock %} diff --git a/authentication/templates/registration/activation_email.txt b/authentication/templates/registration/activation_email.txt new file mode 100644 index 00000000..1ee57137 --- /dev/null +++ b/authentication/templates/registration/activation_email.txt @@ -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. \ No newline at end of file diff --git a/authentication/templates/registration/activation_email_subject.txt b/authentication/templates/registration/activation_email_subject.txt new file mode 100644 index 00000000..5719bb9a --- /dev/null +++ b/authentication/templates/registration/activation_email_subject.txt @@ -0,0 +1 @@ +Confirm your Alliance Auth account email address \ No newline at end of file diff --git a/authentication/templates/registration/password_change_done.html b/authentication/templates/registration/password_change_done.html deleted file mode 100644 index 4751680d..00000000 --- a/authentication/templates/registration/password_change_done.html +++ /dev/null @@ -1,24 +0,0 @@ -{% extends "registered/base.html" %} -{% load bootstrap %} -{% load i18n static %} - - -{% block title %}{% trans 'Password Change' %}{% endblock %} - -{% block content %} -
    -

    {% trans "Change Password" %}

    - -
    -
    -
    -

    - {% trans "Completed" %} -

    -
    -
    -
    -
    - - -{% endblock %} diff --git a/authentication/templates/registration/password_change_form.html b/authentication/templates/registration/password_change_form.html deleted file mode 100644 index 94125ea6..00000000 --- a/authentication/templates/registration/password_change_form.html +++ /dev/null @@ -1,26 +0,0 @@ -{% extends "registered/base.html" %} -{% load bootstrap %} -{% load i18n static %} - -{% block title %}{% trans "Password Change" %}{% endblock %} - -{% block content %} -
    -

    {% trans "Change Password" %}

    - -
    -
    -
    - -
    -
    -
    -
    - - -{% endblock %} diff --git a/authentication/templates/registration/password_reset_base.html b/authentication/templates/registration/password_reset_base.html deleted file mode 100644 index cdc282b4..00000000 --- a/authentication/templates/registration/password_reset_base.html +++ /dev/null @@ -1,44 +0,0 @@ -{% load staticfiles %} -{% load i18n %} -{% load bootstrap %} - - - - - - - - - - - - {{ SITE_NAME }} - {% block page_title %}{% endblock page_title %} - - {% include 'bundles/bootstrap-css.html' %} - {% include 'bundles/fontawesome.html' %} - - - - -{% block content %} -{% endblock content %} -{% include 'bundles/bootstrap-js.html' %} - - diff --git a/authentication/templates/registration/password_reset_complete.html b/authentication/templates/registration/password_reset_complete.html deleted file mode 100644 index 4c1b0e7f..00000000 --- a/authentication/templates/registration/password_reset_complete.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends 'registration/password_reset_base.html' %} -{% load staticfiles %} -{% load i18n %} -{% load bootstrap %} - -{% block page_title %}Login{% endblock %} - -{% block content %} -
    -
    -
    -
    -

    {% trans 'Password reset complete' %}

    - -

    {% trans "Your password has been set." %}

    - - {% trans "Login" %} -
    -
    -
    -
    -{% endblock content %} diff --git a/authentication/templates/registration/password_reset_confirm.html b/authentication/templates/registration/password_reset_confirm.html deleted file mode 100644 index af2760eb..00000000 --- a/authentication/templates/registration/password_reset_confirm.html +++ /dev/null @@ -1,32 +0,0 @@ -{% extends 'registration/password_reset_base.html' %} -{% load staticfiles %} -{% load i18n %} -{% load bootstrap %} - -{% block page_title %}Login{% endblock %} - -{% block content %} -
    -
    -
    -
    - {% if validlink %} - - {% else %} - -

    {% trans 'Password reset unsuccessful' %}

    - -

    {% trans "The password reset link was invalid, possibly because it has already been used. Please request a new password reset." %}

    - - {% endif %} -
    -
    -
    -
    -{% endblock %} diff --git a/authentication/templates/registration/password_reset_done.html b/authentication/templates/registration/password_reset_done.html deleted file mode 100644 index a4c4a1ab..00000000 --- a/authentication/templates/registration/password_reset_done.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends 'registration/password_reset_base.html' %} -{% load staticfiles %} -{% load i18n %} -{% load bootstrap %} - -{% block page_title %}Login{% endblock %} - -{% block content %} -
    -
    -
    -
    -

    {% trans 'Password Reset Success' %}

    - -

    {% trans "We've emailed you instructions for setting your password. You should be receiving them shortly." %}

    - -

    {% trans "If you don't receive an email, please make sure you've entered the address you registered with, and check your spam folder." %}

    - -
    -
    -
    -
    -{% endblock content %} diff --git a/authentication/templates/registration/password_reset_form.html b/authentication/templates/registration/password_reset_form.html deleted file mode 100644 index fd3cc816..00000000 --- a/authentication/templates/registration/password_reset_form.html +++ /dev/null @@ -1,27 +0,0 @@ -{% extends 'registration/password_reset_base.html' %} -{% load staticfiles %} -{% load i18n %} -{% load bootstrap %} - -{% block page_title %}Login{% endblock %} - -{% block content %} -
    -
    -
    -
    - -
    -
    -
    -
    -{% endblock content %} diff --git a/authentication/templates/registration/registration_form.html b/authentication/templates/registration/registration_form.html new file mode 100644 index 00000000..a2506669 --- /dev/null +++ b/authentication/templates/registration/registration_form.html @@ -0,0 +1,14 @@ +{% extends 'public/middle_box.html' %} +{% load bootstrap %} +{% load i18n %} +{% load static %} +{% block title %}Register{% endblock %} +{% block middle_box_content %} + +{% endblock %} diff --git a/authentication/urls.py b/authentication/urls.py index 3a92b81e..4a7a6283 100644 --- a/authentication/urls.py +++ b/authentication/urls.py @@ -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.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'^$', login_required(TemplateView.as_view(template_name='authentication/dashboard.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/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'), diff --git a/authentication/views.py b/authentication/views.py index ed2c1466..f6d42644 100644 --- a/authentication/views.py +++ b/authentication/views.py @@ -9,6 +9,7 @@ from django.contrib import messages from django.contrib.auth.models import User from django.conf import settings from django.core import signing +from django.core.urlresolvers import reverse from esi.decorators import token_required from registration.backends.hmac.views import RegistrationView as BaseRegistrationView, \ ActivationView as BaseActivationView, REGISTRATION_SALT @@ -23,13 +24,19 @@ logger = logging.getLogger(__name__) def main_character_change(request, token): logger.debug("main_character_change called by user %s for character %s" % (request.user, token.character_name)) 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: - 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") + if not CharacterOwnership.objects.filter(character__character_id=token.character_id).exists(): + co = CharacterOwnership.objects.create_by_token(token) + else: + messages.error(request, 'Cannot change main character to %(char)s: character owned by a different account.' % ({'char': token.character_name})) + 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) @@ -38,7 +45,7 @@ def add_character(request, token): 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}))) + messages.error(request, _('Failed to add %(name)s to your account: they already have an account.' % ({'name': token.character_name}))) return redirect('authentication:dashboard') @@ -65,12 +72,12 @@ 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'))) + return redirect(request.POST.get('next', request.GET.get('next', 'authentication: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') + return redirect('registration_register') else: messages.error(request, _('Unable to authenticate as the selected character.')) return redirect(settings.LOGIN_URL) @@ -100,6 +107,13 @@ class RegistrationView(BaseRegistrationView): def get_activation_key(self, user): 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 class ActivationView(BaseActivationView): @@ -121,3 +135,18 @@ class ActivationView(BaseActivationView): user.save() return user 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') diff --git a/corputils/models.py b/corputils/models.py index 432e03c2..39b7b905 100644 --- a/corputils/models.py +++ b/corputils/models.py @@ -145,7 +145,7 @@ class CorpStats(models.Model): class ViewModel(object): def __init__(self, corpstats, user): 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.total_members = len(self.members) self.total_users = corpstats.user_count(self.members) diff --git a/corputils/views.py b/corputils/views.py index a9d63563..d51f3b78 100644 --- a/corputils/views.py +++ b/corputils/views.py @@ -90,7 +90,7 @@ def corpstats_view(request, corp_id=None): members = [] if corpstats: 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: context.update({ diff --git a/services/context_processors.py b/services/context_processors.py index e8ddd680..31a8e2d4 100644 --- a/services/context_processors.py +++ b/services/context_processors.py @@ -7,20 +7,14 @@ def auth_settings(request): return { 'DOMAIN': settings.DOMAIN, 'MUMBLE_URL': settings.MUMBLE_URL, - 'FORUM_URL': settings.FORUM_URL, + 'FORUM_URL': settings.PHPBB3_URL, 'TEAMSPEAK3_PUBLIC_URL': settings.TEAMSPEAK3_PUBLIC_URL, 'DISCORD_SERVER_ID': settings.DISCORD_GUILD_ID, - 'KILLBOARD_URL': settings.KILLBOARD_URL, 'DISCOURSE_URL': settings.DISCOURSE_URL, 'IPS4_URL': settings.IPS4_URL, 'SMF_URL': settings.SMF_URL, 'MARKET_URL': settings.MARKET_URL, - 'EXTERNAL_MEDIA_URL': settings.EXTERNAL_MEDIA_URL, '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, 'SITE_NAME': settings.SITE_NAME, } diff --git a/services/tasks.py b/services/tasks.py index 138dbe84..bcd0aedd 100644 --- a/services/tasks.py +++ b/services/tasks.py @@ -41,7 +41,7 @@ def only_one(function=None, key="", timeout=None): @app.task(bind=True) 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 for svc in ServicesHook.get_services(): try: diff --git a/services/views.py b/services/views.py index 2035787f..a2d6256e 100755 --- a/services/views.py +++ b/services/views.py @@ -1,12 +1,9 @@ from __future__ import unicode_literals -from django.contrib import messages from django.contrib.auth.decorators import login_required from django.shortcuts import render -from django.utils.translation import ugettext_lazy as _ from alliance_auth.hooks import get_hooks -from eveonline.models import EveCharacter from services.forms import FleetFormatterForm import logging @@ -47,7 +44,7 @@ def fleet_formatter_view(request): @login_required def services_view(request): logger.debug("services_view called by user %s" % request.user) - char = request.profile.main_character + char = request.user.profile.main_character context = {'service_ctrls': []} for fn in get_hooks('services_hook'): # Render hooked services controls