diff --git a/alliance_auth/settings.py.example b/alliance_auth/settings.py.example index 38c19c58..17290bab 100644 --- a/alliance_auth/settings.py.example +++ b/alliance_auth/settings.py.example @@ -17,31 +17,6 @@ import djcelery from django.contrib import messages from celery.schedules import crontab -djcelery.setup_loader() - -# Celery configuration -BROKER_URL = 'redis://localhost:6379/0' -CELERYBEAT_SCHEDULER = "djcelery.schedulers.DatabaseScheduler" -CELERYBEAT_SCHEDULE = dict() - -# Build paths inside the project like this: os.path.join(BASE_DIR, ...) -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = '' - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True - -ALLOWED_HOSTS = [] - - -# Application definition - INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', @@ -68,7 +43,7 @@ INSTALLED_APPS = [ 'geelweb.django.navhelper', 'bootstrap_pagination', - # Services + # Services - comment out if not used 'services.modules.mumble', 'services.modules.discord', 'services.modules.discourse', @@ -83,6 +58,25 @@ INSTALLED_APPS = [ 'services.modules.teamspeak3', ] +##################################################### +## +## Django Project Configuration +## +##################################################### +# You shouldn't need to touch most of this. +# Scroll down for Auth Configuration +##################################################### + +djcelery.setup_loader() + +# Celery configuration +BROKER_URL = 'redis://localhost:6379/0' +CELERYBEAT_SCHEDULER = "djcelery.schedulers.DatabaseScheduler" +CELERYBEAT_SCHEDULE = dict() + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', @@ -137,20 +131,6 @@ TEMPLATES = [ WSGI_APPLICATION = 'alliance_auth.wsgi.application' -# Database -# https://docs.djangoproject.com/en/1.10/ref/settings/#databases - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': 'alliance_auth', - 'USER': 'allianceserver', - 'PASSWORD': '', - 'HOST': '127.0.0.1', - 'PORT': '3306', - }, -} - # Password validation # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators @@ -213,20 +193,46 @@ CACHES = { ##################################################### ## -## Auth configuration starts here +## Auth Configuration ## ##################################################### -#################### +############################### +# Required Django Settings +############################### +# SECRET_KEY - Random alphanumeric string for cryptographic signing +# http://www.miniwebtool.com/django-secret-key-generator/ +# DEBUG - True to display stack traces when errors are encountered. +# Set this False once auth is installed and working for security. +# ALLOWED_HOSTS - A list of hosts to serve content on. Wildcards accepted. +# Requests for hosts not listed here will be rejected. +# Example: ['example.com', 'auth.example.com'] +# DATABASES - Auth database connection information. +################################ +SECRET_KEY = '' +DEBUG = True +ALLOWED_HOSTS = [] +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.mysql', + 'NAME': 'alliance_auth', + 'USER': 'allianceserver', + 'PASSWORD': '', + 'HOST': '127.0.0.1', + 'PORT': '3306', + }, +} + +#################################### # SITE_NAME - Name of the auth site. -#################### +#################################### SITE_NAME = 'Alliance Auth' ################# # 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, no leading http:// # EMAIL_HOST - SMTP Server URL # EMAIL_PORT - SMTP Server PORT # EMAIL_HOST_USER - Email Username (for gmail, the entire address) @@ -234,7 +240,7 @@ SITE_NAME = 'Alliance Auth' # EMAIL_USE_TLS - Set to use TLS encryption ################# DEFAULT_FROM_EMAIL = 'no-reply@example.com' -DOMAIN = 'https://example.com' +DOMAIN = 'example.com' EMAIL_HOST = 'smtp.gmail.com' EMAIL_PORT = 587 EMAIL_HOST_USER = '' @@ -255,13 +261,13 @@ ESI_SSO_CALLBACK_URL = '' ################# # Login Settings ################# +# LOGIN_REDIRECT_URL - default destination when logging in if no redirect specified +# LOGOUT_REDIRECT_URL - destination after logging out # Both of these redirects accept values as per the django redirect shortcut # https://docs.djangoproject.com/en/1.10/topics/http/shortcuts/#redirect # - url names eg 'authentication:dashboard' # - relative urls eg '/dashboard' # - absolute urls eg 'http://example.com/dashboard' -# 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 ################## @@ -270,6 +276,12 @@ LOGOUT_REDIRECT_URL = 'authentication:dashboard' LOGIN_TOKEN_SCOPES = ['esi-characters.read_opportunities.v1'] ACCOUNT_ACTIVATION_DAYS = 1 +##################################################### +## +## Service Configuration +## +##################################################### + ##################### # Alliance Market ##################### @@ -402,10 +414,8 @@ DISCOURSE_SSO_SECRET = '' # IPS4 Configuration ##################################### # IPS4_URL - Base URL of the IPS4 install (no trailing slash). Include http:// -# IPS4_API_KEY - API key provided by IPS4 ##################################### IPS4_URL = '' -IPS4_API_KEY = '' IPS4_DB = { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'alliance_ips4', @@ -452,9 +462,11 @@ FLEETUP_USER_ID = '' FLEETUP_API_ID = '' FLEETUP_GROUP_ID = '' -##################################### -# Logging Configuration -##################################### +##################################################### +## +## Logging Configuration +## +##################################################### # Set log_file and console level to desired state: # DEBUG - basically stack trace, explains every step # INFO - model creation, deletion, updates, etc @@ -559,12 +571,22 @@ LOGGING = { 'level': 'DEBUG', }, 'django': { - 'handlers': ['log_file', 'console', 'notifications'], + 'handlers': ['log_file', 'console'], 'level': 'ERROR', }, } } +##################################################### +## +## Magic Block +## +##################################################### +# This block automagically inserts needed settings if +# certain services are installed. +# Don't touch. +######################################### + # Conditionally add databases only if configured if 'services.modules.phpbb3' in INSTALLED_APPS: DATABASES['phpbb3'] = PHPBB3_DB diff --git a/authentication/admin.py b/authentication/admin.py index de0acd74..f493ced7 100644 --- a/authentication/admin.py +++ b/authentication/admin.py @@ -5,9 +5,12 @@ 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 django.db.models.signals import post_save from authentication.models import State, get_guest_state, CharacterOwnership, UserProfile +from authentication.signals import reassess_on_profile_save from alliance_auth.hooks import get_hooks from services.hooks import ServicesHook +from services.tasks import validate_services def make_service_hooks_update_groups_action(service): @@ -92,6 +95,18 @@ class StateForm(forms.ModelForm): class StateAdmin(admin.ModelAdmin): form = StateForm + fieldsets = ( + (None, { + 'fields': ('name', 'permissions', 'priority'), + }), + ('Membership', { + 'classes': ('collapse',), + 'fields': ('public', 'member_characters', 'member_corporations', 'member_alliances'), + }) + ) + + filter_horizontal = ['member_characters', 'member_corporations', 'member_alliances', 'permissions'] + @staticmethod def has_delete_permission(request, obj=None): if obj == get_guest_state(): @@ -99,4 +114,18 @@ class StateAdmin(admin.ModelAdmin): admin.site.register(CharacterOwnership) -admin.site.register(UserProfile) + + +class UserProfileAdminForm(forms.ModelForm): + def save(self, *args, **kwargs): + # prevent state reassessment to allow manually overriding states + post_save.disconnect(reassess_on_profile_save, sender=UserProfile) + model = super(UserProfileAdminForm, self).save(*args, **kwargs) + post_save.connect(reassess_on_profile_save, sender=UserProfile) + validate_services(model.user) + return model + + +@admin.register(UserProfile) +class UserProfileAdmin(admin.ModelAdmin): + form = UserProfileAdminForm diff --git a/authentication/migrations/0015_user_profiles.py b/authentication/migrations/0015_user_profiles.py index 8a858749..1ad439d0 100644 --- a/authentication/migrations/0015_user_profiles.py +++ b/authentication/migrations/0015_user_profiles.py @@ -149,10 +149,12 @@ def recreate_authservicesinfo(apps, schema_editor): # 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}) + 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'): + 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}) @@ -196,11 +198,15 @@ class Migration(migrations.Migration): 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(blank=True,to='eveonline.EveAllianceInfo')), - ('member_characters', models.ManyToManyField(blank=True,to='eveonline.EveCharacter')), - ('member_corporations', models.ManyToManyField(blank=True,to='eveonline.EveCorporationInfo')), + ('priority', models.IntegerField(unique=True, + help_text="Users get assigned the state with the highest priority available to them.")), + ('public', models.BooleanField(default=False, help_text="Make this state available to any character.")), + ('member_alliances', models.ManyToManyField(blank=True, to='eveonline.EveAllianceInfo', + help_text="Alliances to whose members this state is available.")), + ('member_characters', models.ManyToManyField(blank=True, to='eveonline.EveCharacter', + help_text="Characters to which this state is available.")), + ('member_corporations', models.ManyToManyField(blank=True, to='eveonline.EveCorporationInfo', + help_text="Corporations to whose members this state is available.")), ('permissions', models.ManyToManyField(blank=True, to='auth.Permission')), ], options={ @@ -215,7 +221,8 @@ 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', default=authentication.models.get_guest_state_pk)), + 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)), ], diff --git a/authentication/models.py b/authentication/models.py index fa98c1ac..80b4b9d6 100755 --- a/authentication/models.py +++ b/authentication/models.py @@ -4,6 +4,8 @@ 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 +from notifications import notify +from django.utils.translation import ugettext_lazy as _ import logging logger = logging.getLogger(__name__) @@ -13,12 +15,16 @@ logger = logging.getLogger(__name__) class State(models.Model): name = models.CharField(max_length=20, unique=True) permissions = models.ManyToManyField(Permission, blank=True) - priority = models.IntegerField(unique=True) + priority = models.IntegerField(unique=True, + help_text="Users get assigned the state with the highest priority available to them.") - 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) + member_characters = models.ManyToManyField(EveCharacter, blank=True, + help_text="Characters to which this state is available.") + member_corporations = models.ManyToManyField(EveCorporationInfo, blank=True, + help_text="Corporations to whose members this state is available.") + member_alliances = models.ManyToManyField(EveAllianceInfo, blank=True, + help_text="Alliances to whose members this state is available.") + public = models.BooleanField(default=False, help_text="Make this state available to any character.") objects = StateManager() @@ -62,6 +68,9 @@ class UserProfile(models.Model): if commit: logger.info('Updating {} state to {}'.format(self.user, self.state)) self.save(update_fields=['state']) + notify(self.user, _('State Changed'), + _('Your user state has been changed to %(state)s') % ({'state': state}), + 'info') def __str__(self): return str(self.user) diff --git a/authentication/signals.py b/authentication/signals.py index e4ffe373..399b92e7 100644 --- a/authentication/signals.py +++ b/authentication/signals.py @@ -108,3 +108,10 @@ def assign_state_on_reactivate(sender, instance, *args, **kwargs): # 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() + + +@receiver(post_save, sender=EveCharacter) +def check_state_on_character_update(sender, instance, *args, **kwargs): + # if this is a main character updating, check that user's state + if instance.userprofile: + instance.userprofile.assign_state() diff --git a/authentication/templates/registered/base.html b/authentication/templates/registered/base.html index fc351abd..b67e9f17 100644 --- a/authentication/templates/registered/base.html +++ b/authentication/templates/registered/base.html @@ -80,10 +80,6 @@