mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2026-02-04 06:06:19 +01:00
Compare commits
72 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce66bdcbd4 | ||
|
|
f65e563c0c | ||
|
|
e860ba6c22 | ||
|
|
50b6605a43 | ||
|
|
d181200642 | ||
|
|
386ba25a44 | ||
|
|
5331d194df | ||
|
|
814ecd233e | ||
|
|
f9a8ac4e9b | ||
|
|
1bd5eecd54 | ||
|
|
2fa1d9998d | ||
|
|
9d9cfebd9e | ||
|
|
cc8a7a18d2 | ||
|
|
552c795041 | ||
|
|
3d757e8d90 | ||
|
|
1b5ecaed80 | ||
|
|
77c93ed96b | ||
|
|
3eeed99af2 | ||
|
|
a143dfbb37 | ||
|
|
6b1da3b18a | ||
|
|
f0894f3415 | ||
|
|
539295c1b7 | ||
|
|
54f91a5bfb | ||
|
|
f3c0d05c39 | ||
|
|
9f9cc7ed42 | ||
|
|
814b2da0ca | ||
|
|
7a9bb0c84b | ||
|
|
36ae2af29b | ||
|
|
d192f23e6e | ||
|
|
67cd0cd55c | ||
|
|
9e53d8b429 | ||
|
|
f5abf82b95 | ||
|
|
8dd3a25b52 | ||
|
|
d0aa46db08 | ||
|
|
f0ff70566b | ||
|
|
efecf5113b | ||
|
|
980569de68 | ||
|
|
9c74952607 | ||
|
|
70c2a4a6e4 | ||
|
|
99b136b824 | ||
|
|
ae4116c0f6 | ||
|
|
3080d7d868 | ||
|
|
08cf8ae1d6 | ||
|
|
3ed0f873f3 | ||
|
|
5060d3f408 | ||
|
|
ef24bea562 | ||
|
|
c18efaa33d | ||
|
|
b6b14f6f1c | ||
|
|
a90a52f426 | ||
|
|
bd5ea38446 | ||
|
|
f8248f46e5 | ||
|
|
b09c454bf0 | ||
|
|
d825689da4 | ||
|
|
a64dda2a2e | ||
|
|
8ce8789631 | ||
|
|
2b2f367c30 | ||
|
|
4d194457d8 | ||
|
|
6f7cf8805d | ||
|
|
36e39503c8 | ||
|
|
e7a24c9cd4 | ||
|
|
bd8a8922cc | ||
|
|
396b2e0fb6 | ||
|
|
36e382fadb | ||
|
|
d2666f2440 | ||
|
|
397ca97f0f | ||
|
|
631bb439a4 | ||
|
|
a4003e188e | ||
|
|
f4a9ba2db8 | ||
|
|
895a62c475 | ||
|
|
ac5a0d9dcb | ||
|
|
b8644d5c93 | ||
|
|
4d8baf1af0 |
27
README.md
27
README.md
@@ -7,8 +7,7 @@ Alliance Auth
|
||||
[](https://coveralls.io/github/allianceauth/allianceauth?branch=master)
|
||||
|
||||
|
||||
EVE service auth to help corps, alliances, and coalitions manage services.
|
||||
Built for "The 99 Percent" open for anyone to use.
|
||||
An auth system for EVE Online to help in-game organizations manage online service access.
|
||||
|
||||
[Read the docs here.](http://allianceauth.rtfd.io)
|
||||
|
||||
@@ -17,22 +16,18 @@ Built for "The 99 Percent" open for anyone to use.
|
||||
|
||||
Active Developers:
|
||||
|
||||
- [Adarnof](https://github.com/Adarnof)
|
||||
- [Basraah](https://github.com/basraah)
|
||||
|
||||
- [Adarnof](https://github.com/adarnof/)
|
||||
- [Basraah](https://github.com/basraah/)
|
||||
|
||||
Beta Testers / Bug Fixers:
|
||||
|
||||
- [ghoti](https://github.com/ghoti)
|
||||
- [mmolitor87](https://github.com/mmolitor87)
|
||||
- [ghoti](https://github.com/ghoti/)
|
||||
- [mmolitor87](https://github.com/mmolitor87/)
|
||||
- [kaezon](https://github.com/kaezon/)
|
||||
- [orbitroom](https://github.com/orbitroom/)
|
||||
- [tehfiend](https://github.com/tehfiend/)
|
||||
|
||||
Special thanks to [Nikdoof](https://github.com/nikdoof/), as his [auth](https://github.com/nikdoof/test-auth) was the foundation for the original work on this project.
|
||||
|
||||
Past Beta Testers / Bug Fixers:
|
||||
|
||||
- TrentBartlem (Testing and Bug Fixes)
|
||||
- IskFiend (Bug Fixes and Server Configuration)
|
||||
- Mr McClain (Bug Fixes and server configuration)
|
||||
|
||||
Special Thanks:
|
||||
|
||||
- Thanks to Nikdoof, without his old auth implementation this project wouldn't be as far as it is now.
|
||||
### Contributing
|
||||
Make sure you have signed the [License Agreement](https://developers.eveonline.com/resource/license-agreement) by logging in at [https://developers.eveonline.com](https://developers.eveonline.com) before submitting any pull requests.
|
||||
@@ -1,7 +1,7 @@
|
||||
# This will make sure the app is always imported when
|
||||
# Django starts so that shared_task will use this app.
|
||||
|
||||
__version__ = '2.0b2'
|
||||
__version__ = '2.0b3'
|
||||
NAME = 'Alliance Auth v%s' % __version__
|
||||
default_app_config = 'allianceauth.apps.AllianceAuthConfig'
|
||||
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||
from django.contrib.auth.models import User, Permission
|
||||
from django.contrib.auth.models import User as BaseUser, Permission as BasePermission
|
||||
from django.utils.text import slugify
|
||||
from django.db.models import Q
|
||||
from allianceauth.services.hooks import ServicesHook
|
||||
|
||||
from django.db.models.signals import pre_save, post_save, pre_delete, post_delete, m2m_changed
|
||||
from django.dispatch import receiver
|
||||
from allianceauth.authentication.models import State, get_guest_state, CharacterOwnership, UserProfile
|
||||
from allianceauth.hooks import get_hooks
|
||||
from allianceauth.eveonline.models import EveCharacter
|
||||
from django.forms import ModelForm
|
||||
|
||||
|
||||
def make_service_hooks_update_groups_action(service):
|
||||
@@ -38,6 +42,47 @@ def make_service_hooks_sync_nickname_action(service):
|
||||
return sync_nickname
|
||||
|
||||
|
||||
class QuerysetModelForm(ModelForm):
|
||||
# allows specifying FK querysets through kwarg
|
||||
def __init__(self, querysets=None, *args, **kwargs):
|
||||
querysets = querysets or {}
|
||||
super().__init__(*args, **kwargs)
|
||||
for field, qs in querysets.items():
|
||||
self.fields[field].queryset = qs
|
||||
|
||||
|
||||
class UserProfileInline(admin.StackedInline):
|
||||
model = UserProfile
|
||||
readonly_fields = ('state',)
|
||||
form = QuerysetModelForm
|
||||
verbose_name = ''
|
||||
verbose_name_plural = 'Profile'
|
||||
|
||||
def get_formset(self, request, obj=None, **kwargs):
|
||||
# main_character field can only show current value or unclaimed alts
|
||||
# if superuser, allow selecting from any unclaimed main
|
||||
query = Q()
|
||||
if obj and obj.profile.main_character:
|
||||
query |= Q(pk=obj.profile.main_character_id)
|
||||
if request.user.is_superuser:
|
||||
query |= Q(userprofile__isnull=True)
|
||||
else:
|
||||
query |= Q(character_ownership__user=obj)
|
||||
qs = EveCharacter.objects.filter(query)
|
||||
formset = super().get_formset(request, obj=obj, **kwargs)
|
||||
|
||||
def get_kwargs(self, index):
|
||||
return {'querysets': {'main_character': EveCharacter.objects.filter(query)}}
|
||||
formset.get_form_kwargs = get_kwargs
|
||||
return formset
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
|
||||
class UserAdmin(BaseUserAdmin):
|
||||
"""
|
||||
Extending Django's UserAdmin model
|
||||
@@ -62,6 +107,25 @@ class UserAdmin(BaseUserAdmin):
|
||||
|
||||
return actions
|
||||
list_filter = BaseUserAdmin.list_filter + ('profile__state',)
|
||||
inlines = BaseUserAdmin.inlines + [UserProfileInline]
|
||||
list_display = ('username', 'email', 'get_main_character', 'get_state', 'is_active')
|
||||
|
||||
def get_main_character(self, obj):
|
||||
return obj.profile.main_character
|
||||
get_main_character.short_description = "Main Character"
|
||||
|
||||
def get_state(self, obj):
|
||||
return obj.profile.state
|
||||
get_state.short_description = "State"
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
return request.user.has_perm('auth.change_user')
|
||||
|
||||
def has_add_permission(self, request, obj=None):
|
||||
return request.user.has_perm('auth.add_user')
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
return request.user.has_perm('auth.delete_user')
|
||||
|
||||
|
||||
@admin.register(State)
|
||||
@@ -96,31 +160,19 @@ class StateAdmin(admin.ModelAdmin):
|
||||
return obj.userprofile_set.all().count()
|
||||
|
||||
|
||||
@admin.register(UserProfile)
|
||||
class UserProfileAdmin(admin.ModelAdmin):
|
||||
readonly_fields = ('user', 'state')
|
||||
search_fields = ('user__username', 'main_character__character_name')
|
||||
list_filter = ('state',)
|
||||
list_display = ('user', 'main_character')
|
||||
actions = None
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
|
||||
@admin.register(CharacterOwnership)
|
||||
class CharacterOwnershipAdmin(admin.ModelAdmin):
|
||||
list_display = ('user', 'character')
|
||||
search_fields = ('user__username', 'character__character_name', 'character__corporation_name', 'character__alliance_name')
|
||||
readonly_fields = ('owner_hash', 'character')
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
|
||||
|
||||
class PermissionAdmin(admin.ModelAdmin):
|
||||
actions = None
|
||||
readonly_fields = [field.name for field in Permission._meta.fields]
|
||||
readonly_fields = [field.name for field in BasePermission._meta.fields]
|
||||
list_display = ('admin_name', 'name', 'codename', 'content_type')
|
||||
list_filter = ('content_type__app_label',)
|
||||
|
||||
@@ -134,23 +186,61 @@ class PermissionAdmin(admin.ModelAdmin):
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
def has_module_permission(self, request):
|
||||
return True
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
# can see list but not edit it
|
||||
return not obj
|
||||
|
||||
|
||||
# Hack to allow registration of django.contrib.auth models in our authentication app
|
||||
class ProxyUser(User):
|
||||
class User(BaseUser):
|
||||
class Meta:
|
||||
proxy = True
|
||||
verbose_name = User._meta.verbose_name
|
||||
verbose_name_plural = User._meta.verbose_name_plural
|
||||
verbose_name = BaseUser._meta.verbose_name
|
||||
verbose_name_plural = BaseUser._meta.verbose_name_plural
|
||||
|
||||
|
||||
class ProxyPermission(Permission):
|
||||
class Permission(BasePermission):
|
||||
class Meta:
|
||||
proxy = True
|
||||
verbose_name = Permission._meta.verbose_name
|
||||
verbose_name_plural = Permission._meta.verbose_name_plural
|
||||
verbose_name = BasePermission._meta.verbose_name
|
||||
verbose_name_plural = BasePermission._meta.verbose_name_plural
|
||||
|
||||
|
||||
try:
|
||||
admin.site.unregister(User)
|
||||
admin.site.unregister(BaseUser)
|
||||
finally:
|
||||
admin.site.register(ProxyUser, UserAdmin)
|
||||
admin.site.register(ProxyPermission, PermissionAdmin)
|
||||
admin.site.register(User, UserAdmin)
|
||||
admin.site.register(Permission, PermissionAdmin)
|
||||
|
||||
|
||||
@receiver(pre_save, sender=User)
|
||||
def redirect_pre_save(sender, signal=None, *args, **kwargs):
|
||||
pre_save.send(BaseUser, *args, **kwargs)
|
||||
|
||||
|
||||
@receiver(post_save, sender=User)
|
||||
def redirect_post_save(sender, signal=None, *args, **kwargs):
|
||||
post_save.send(BaseUser, *args, **kwargs)
|
||||
|
||||
|
||||
@receiver(pre_delete, sender=User)
|
||||
def redirect_pre_delete(sender, signal=None, *args, **kwargs):
|
||||
pre_delete.send(BaseUser, *args, **kwargs)
|
||||
|
||||
|
||||
@receiver(post_delete, sender=User)
|
||||
def redirect_post_delete(sender, signal=None, *args, **kwargs):
|
||||
post_delete.send(BaseUser, *args, **kwargs)
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=User.groups.through)
|
||||
def redirect_m2m_changed_groups(sender, signal=None, *args, **kwargs):
|
||||
m2m_changed.send(BaseUser, *args, **kwargs)
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=User.user_permissions.through)
|
||||
def redirect_m2m_changed_permissions(sender, signal=None, *args, **kwargs):
|
||||
m2m_changed.send(BaseUser, *args, **kwargs)
|
||||
|
||||
37
allianceauth/authentication/decorators.py
Normal file
37
allianceauth/authentication/decorators.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from django.conf.urls import include
|
||||
from functools import wraps
|
||||
from django.shortcuts import redirect
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
||||
|
||||
def user_has_main_character(user):
|
||||
return bool(user.profile.main_character)
|
||||
|
||||
|
||||
def decorate_url_patterns(urls, decorator):
|
||||
url_list, app_name, namespace = include(urls)
|
||||
|
||||
def process_patterns(url_patterns):
|
||||
for pattern in url_patterns:
|
||||
if hasattr(pattern, 'url_patterns'):
|
||||
# this is an include - apply to all nested patterns
|
||||
process_patterns(pattern.url_patterns)
|
||||
else:
|
||||
# this is a pattern
|
||||
pattern.callback = decorator(pattern.callback)
|
||||
|
||||
process_patterns(url_list)
|
||||
return url_list, app_name, namespace
|
||||
|
||||
|
||||
def main_character_required(view_func):
|
||||
@wraps(view_func)
|
||||
def _wrapped_view(request, *args, **kwargs):
|
||||
if user_has_main_character(request.user):
|
||||
return view_func(request, *args, **kwargs)
|
||||
|
||||
messages.error(request, _('A main character is required to perform that action. Add one below.'))
|
||||
return redirect('authentication:dashboard')
|
||||
return login_required(_wrapped_view)
|
||||
@@ -145,7 +145,7 @@ def recreate_authservicesinfo(apps, schema_editor):
|
||||
User = apps.get_model('auth', 'User')
|
||||
|
||||
# recreate all missing AuthServicesInfo models
|
||||
AuthServicesInfo.objects.bulk_create([AuthServicesInfo(user=u.pk) for u in User.objects.all()])
|
||||
AuthServicesInfo.objects.bulk_create([AuthServicesInfo(user_id=u.pk) for u in User.objects.all()])
|
||||
|
||||
# repopulate main characters
|
||||
for profile in UserProfile.objects.exclude(main_character__isnull=True).select_related('user', 'main_character'):
|
||||
@@ -203,7 +203,6 @@ class Migration(migrations.Migration):
|
||||
('permissions', models.ManyToManyField(blank=True, to='auth.Permission')),
|
||||
],
|
||||
options={
|
||||
'default_permissions': ('change',),
|
||||
'ordering': ['-priority'],
|
||||
},
|
||||
),
|
||||
@@ -233,7 +232,7 @@ class Migration(migrations.Migration):
|
||||
),
|
||||
migrations.RunPython(disable_passwords, migrations.RunPython.noop),
|
||||
migrations.CreateModel(
|
||||
name='ProxyPermission',
|
||||
name='Permission',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
@@ -247,7 +246,7 @@ class Migration(migrations.Migration):
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ProxyUser',
|
||||
name='User',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
|
||||
@@ -3,7 +3,7 @@ import logging
|
||||
from .models import CharacterOwnership, UserProfile, get_guest_state, State
|
||||
from django.contrib.auth.models import User
|
||||
from django.db.models import Q
|
||||
from django.db.models.signals import post_save, pre_delete, m2m_changed, pre_save
|
||||
from django.db.models.signals import pre_save, post_save, pre_delete, m2m_changed
|
||||
from django.dispatch import receiver, Signal
|
||||
from esi.models import Token
|
||||
|
||||
@@ -11,7 +11,6 @@ from allianceauth.eveonline.models import EveCharacter
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
state_changed = Signal(providing_args=['user', 'state'])
|
||||
|
||||
|
||||
@@ -32,23 +31,27 @@ def trigger_state_check(state):
|
||||
@receiver(m2m_changed, sender=State.member_characters.through)
|
||||
def state_member_characters_changed(sender, instance, action, *args, **kwargs):
|
||||
if action.startswith('post_'):
|
||||
logger.debug('State {} member characters changed. Re-evaluating membership.'.format(instance))
|
||||
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_'):
|
||||
logger.debug('State {} member corporations changed. Re-evaluating membership.'.format(instance))
|
||||
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_'):
|
||||
logger.debug('State {} member alliances changed. Re-evaluating membership.'.format(instance))
|
||||
trigger_state_check(instance)
|
||||
|
||||
|
||||
@receiver(post_save, sender=State)
|
||||
def state_saved(sender, instance, *args, **kwargs):
|
||||
logger.debug('State {} saved. Re-evaluating membership.'.format(instance))
|
||||
trigger_state_check(instance)
|
||||
|
||||
|
||||
@@ -59,6 +62,7 @@ def reassess_on_profile_save(sender, instance, created, *args, **kwargs):
|
||||
if not created:
|
||||
update_fields = kwargs.pop('update_fields', []) or []
|
||||
if 'state' not in update_fields:
|
||||
logger.debug('Profile for {} saved without state change. Re-evaluating state.'.format(instance.user))
|
||||
instance.assign_state()
|
||||
|
||||
|
||||
@@ -66,12 +70,15 @@ def reassess_on_profile_save(sender, instance, created, *args, **kwargs):
|
||||
def create_required_models(sender, instance, created, *args, **kwargs):
|
||||
# ensure all users have a model
|
||||
if created:
|
||||
logger.debug('User {} created. Creating default UserProfile.'.format(instance))
|
||||
UserProfile.objects.get_or_create(user=instance)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Token)
|
||||
def record_character_ownership(sender, instance, created, *args, **kwargs):
|
||||
if created:
|
||||
logger.debug('New token for {0} character {1} saved. Evaluating ownership.'.format(instance.user,
|
||||
instance.character_name))
|
||||
if instance.user:
|
||||
query = Q(owner_hash=instance.character_owner_hash) & Q(user=instance.user)
|
||||
else:
|
||||
@@ -80,10 +87,15 @@ def record_character_ownership(sender, instance, created, *args, **kwargs):
|
||||
CharacterOwnership.objects.filter(character__character_id=instance.character_id).exclude(query).delete()
|
||||
# create character if needed
|
||||
if EveCharacter.objects.filter(character_id=instance.character_id).exists() is False:
|
||||
logger.debug('Token is for a new character. Creating model for {0} ({1})'.format(instance.character_name,
|
||||
instance.character_id))
|
||||
EveCharacter.objects.create_character(instance.character_id)
|
||||
char = EveCharacter.objects.get(character_id=instance.character_id)
|
||||
# check if we need to create ownership
|
||||
if instance.user and not CharacterOwnership.objects.filter(character__character_id=instance.character_id).exists():
|
||||
if instance.user and not CharacterOwnership.objects.filter(
|
||||
character__character_id=instance.character_id).exists():
|
||||
logger.debug("Character {0} is not yet owned. Assigning ownership to {1}".format(instance.character_name,
|
||||
instance.user))
|
||||
CharacterOwnership.objects.update_or_create(character=char,
|
||||
defaults={'owner_hash': instance.character_owner_hash,
|
||||
'user': instance.user})
|
||||
@@ -92,6 +104,8 @@ def record_character_ownership(sender, instance, created, *args, **kwargs):
|
||||
@receiver(pre_delete, sender=CharacterOwnership)
|
||||
def validate_main_character(sender, instance, *args, **kwargs):
|
||||
if instance.user.profile.main_character == instance.character:
|
||||
logger.debug("Ownership of a main character {0} has been revoked. Resetting {1} main character.".format(
|
||||
instance.character, instance.user))
|
||||
# clear main character as user no longer owns them
|
||||
instance.user.profile.main_character = None
|
||||
instance.user.profile.save()
|
||||
@@ -100,8 +114,15 @@ def validate_main_character(sender, instance, *args, **kwargs):
|
||||
@receiver(pre_delete, sender=Token)
|
||||
def validate_main_character_token(sender, instance, *args, **kwargs):
|
||||
if UserProfile.objects.filter(main_character__character_id=instance.character_id).exists():
|
||||
logger.debug(
|
||||
"Token for a main character {0} is being deleted. Ensuring there are valid tokens to refresh.".format(
|
||||
instance.character_name))
|
||||
profile = UserProfile.objects.get(main_character__character_id=instance.character_id)
|
||||
if not Token.objects.filter(character_id=instance.character_id).filter(user=profile.user).exclude(pk=instance.pk).exists():
|
||||
if not Token.objects.filter(character_id=instance.character_id).filter(user=profile.user).exclude(
|
||||
pk=instance.pk).require_valid().exists():
|
||||
logger.debug(
|
||||
"No remaining tokens to validate {0} ownership of main character {1}. Resetting main character.".format(
|
||||
profile.user, profile.main_character))
|
||||
# clear main character as we can no longer verify ownership
|
||||
profile.main_character = None
|
||||
profile.save()
|
||||
@@ -114,8 +135,11 @@ def assign_state_on_active_change(sender, instance, *args, **kwargs):
|
||||
old_instance = User.objects.get(pk=instance.pk)
|
||||
if old_instance.is_active != instance.is_active:
|
||||
if instance.is_active:
|
||||
logger.debug("User {0} has been activated. Assigning state.".format(instance))
|
||||
instance.profile.assign_state()
|
||||
else:
|
||||
logger.debug(
|
||||
"User {0} has been deactivated. Revoking state and assigning to guest state.".format(instance))
|
||||
instance.profile.state = get_guest_state()
|
||||
instance.profile.save(update_fields=['state'])
|
||||
|
||||
@@ -124,6 +148,8 @@ def assign_state_on_active_change(sender, instance, *args, **kwargs):
|
||||
def check_state_on_character_update(sender, instance, *args, **kwargs):
|
||||
# if this is a main character updating, check that user's state
|
||||
try:
|
||||
logger.debug("Character {0} has been saved. Assessing owner's state for changes.".format(instance))
|
||||
instance.userprofile.assign_state()
|
||||
except UserProfile.DoesNotExist:
|
||||
logger.debug("Character {0} is not a main character. No state assessment required.".format(instance))
|
||||
pass
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td class="text-center"><img class="ra-avatar"
|
||||
src="https://image.eveonline.com/Character/{{ main.character_id }}_128.jpg">
|
||||
src="{{ main.portrait_url_128 }}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -57,7 +57,7 @@
|
||||
</div>
|
||||
{% endwith %}
|
||||
{% else %}
|
||||
<div class="alert alert-danger" role="alert">{% trans "Missing main character model." %}</div>
|
||||
<div class="alert alert-danger" role="alert">{% trans "No main character set." %}</div>
|
||||
{% endif %}
|
||||
<div class="clearfix"></div>
|
||||
<div class="col-xs-6">
|
||||
@@ -102,8 +102,7 @@
|
||||
{% for ownership in request.user.character_ownerships.all %}
|
||||
{% with ownership.character as char %}
|
||||
<tr>
|
||||
<td class="text-center"><img class="ra-avatar img-circle"
|
||||
src="https://image.eveonline.com/Character/{{ char.character_id }}_32.jpg">
|
||||
<td class="text-center"><img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}">
|
||||
</td>
|
||||
<td class="text-center">{{ char.character_name }}</td>
|
||||
<td class="text-center">{{ char.corporation_name }}</td>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<style>
|
||||
body {
|
||||
background: url('{% static 'authentication/img/background.jpg' %}') no-repeat scroll;
|
||||
background: url('{% static 'authentication/img/background.jpg' %}') no-repeat center center fixed;
|
||||
-webkit-background-size: cover;
|
||||
-moz-background-size: cover;
|
||||
-o-background-size: cover;
|
||||
@@ -48,4 +48,4 @@
|
||||
{% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
<div class="dropdown">
|
||||
<form action="{% url 'set_language' %}" method="post">
|
||||
{% csrf_token %}
|
||||
<input name="next" type="hidden" value="{{ request.get_full_path|slice:'3:' }}" />
|
||||
<select onchange="this.form.submit()" class="form-control" id="lang-select" name="language">
|
||||
{% get_language_info_list for LANGUAGES as languages %}
|
||||
{% for language in languages %}
|
||||
@@ -12,4 +11,4 @@
|
||||
{% endfor %}
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ You're receiving this email because someone has entered this email address while
|
||||
|
||||
If this was you, please go to the following URL to confirm your email address:
|
||||
|
||||
{{ url }}
|
||||
{{ scheme }}://{{ url }}
|
||||
|
||||
This link will expire in {{ expiration_days }} day(s).
|
||||
|
||||
|
||||
@@ -8,6 +8,61 @@ from .backends import StateBackend
|
||||
from .tasks import check_character_ownership
|
||||
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo, EveAllianceInfo
|
||||
from esi.models import Token
|
||||
from allianceauth.authentication.decorators import main_character_required
|
||||
from django.test.client import RequestFactory
|
||||
from django.http.response import HttpResponse
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.conf import settings
|
||||
from django.shortcuts import reverse
|
||||
from urllib import parse
|
||||
|
||||
MODULE_PATH = 'allianceauth.authentication'
|
||||
|
||||
|
||||
class DecoratorTestCase(TestCase):
|
||||
@staticmethod
|
||||
@main_character_required
|
||||
def dummy_view(*args, **kwargs):
|
||||
return HttpResponse(status=200)
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.main_user = AuthUtils.create_user('main_user', disconnect_signals=True)
|
||||
cls.no_main_user = AuthUtils.create_user('no_main_user', disconnect_signals=True)
|
||||
main_character = EveCharacter.objects.create(
|
||||
character_id=1,
|
||||
character_name='Main Character',
|
||||
corporation_id=1,
|
||||
corporation_name='Corp',
|
||||
corporation_ticker='CORP',
|
||||
)
|
||||
CharacterOwnership.objects.create(user=cls.main_user, character=main_character, owner_hash='1')
|
||||
cls.main_user.profile.main_character = main_character
|
||||
|
||||
def setUp(self):
|
||||
self.request = RequestFactory().get('/test/')
|
||||
|
||||
@mock.patch(MODULE_PATH + '.decorators.messages')
|
||||
def test_login_redirect(self, m):
|
||||
setattr(self.request, 'user', AnonymousUser())
|
||||
response = self.dummy_view(self.request)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
url = getattr(response, 'url', None)
|
||||
self.assertEqual(parse.urlparse(url).path, reverse(settings.LOGIN_URL))
|
||||
|
||||
@mock.patch(MODULE_PATH + '.decorators.messages')
|
||||
def test_main_character_redirect(self, m):
|
||||
setattr(self.request, 'user', self.no_main_user)
|
||||
response = self.dummy_view(self.request)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
url = getattr(response, 'url', None)
|
||||
self.assertEqual(url, reverse('authentication:dashboard'))
|
||||
|
||||
@mock.patch(MODULE_PATH + '.decorators.messages')
|
||||
def test_successful_request(self, m):
|
||||
setattr(self.request, 'user', self.main_user)
|
||||
response = self.dummy_view(self.request)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class BackendTestCase(TestCase):
|
||||
|
||||
@@ -10,6 +10,7 @@ from django.urls import reverse
|
||||
from django.shortcuts import redirect
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from esi.decorators import token_required
|
||||
from esi.models import Token
|
||||
from registration.backends.hmac.views import RegistrationView as BaseRegistrationView, \
|
||||
ActivationView as BaseActivationView, REGISTRATION_SALT
|
||||
from registration.signals import user_registered
|
||||
@@ -71,17 +72,22 @@ have the email address embedded much like the username. Key creation and decodin
|
||||
@token_required(new=True, scopes=settings.LOGIN_TOKEN_SCOPES)
|
||||
def sso_login(request, token):
|
||||
user = authenticate(token=token)
|
||||
if user and user.is_active:
|
||||
login(request, user)
|
||||
return redirect(request.POST.get('next', request.GET.get('next', '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('registration_register')
|
||||
else:
|
||||
messages.error(request, _('Unable to authenticate as the selected character.'))
|
||||
return redirect(settings.LOGIN_URL)
|
||||
if user:
|
||||
token.user = user
|
||||
if Token.objects.exclude(pk=token.pk).equivalent_to(token).require_valid().exists():
|
||||
token.delete()
|
||||
else:
|
||||
token.save()
|
||||
if user.is_active:
|
||||
login(request, user)
|
||||
return redirect(request.POST.get('next', request.GET.get('next', 'authentication:dashboard')))
|
||||
elif 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('registration_register')
|
||||
messages.error(request, _('Unable to authenticate as the selected character.'))
|
||||
return redirect(settings.LOGIN_URL)
|
||||
|
||||
|
||||
# Step 2
|
||||
|
||||
@@ -121,8 +121,11 @@ class CorpStats(models.Model):
|
||||
m.main_character and int(m.main_character.character_id) == int(
|
||||
m.character_id)])
|
||||
|
||||
def visible_to(self, user):
|
||||
return CorpStats.objects.filter(pk=self.pk).visible_to(user).exists()
|
||||
|
||||
def can_update(self, user):
|
||||
return user.is_superuser or user == self.token.user
|
||||
return self.token.user == user or self.visible_to(user)
|
||||
|
||||
def corp_logo(self, size=128):
|
||||
return "https://image.eveonline.com/Corporation/%s_%s.png" % (self.corp.corporation_id, size)
|
||||
@@ -179,4 +182,4 @@ class CorpMember(models.Model):
|
||||
if item.startswith('portrait_url_'):
|
||||
size = item.strip('portrait_url_')
|
||||
return self.portrait_url(size)
|
||||
return super(CorpMember, self).__getattr__(item)
|
||||
return self.__getattribute__(item)
|
||||
@@ -9,9 +9,9 @@
|
||||
<tr>
|
||||
<td class="text-center col-lg-6
|
||||
{% if corpstats.corp.alliance %}{% else %}col-lg-offset-3{% endif %}"><img
|
||||
class="ra-avatar" src="{{ corpstats.corp_logo }}"></td>
|
||||
class="ra-avatar" src="{{ corpstats.corp.logo_url_128 }}"></td>
|
||||
{% if corpstats.corp.alliance %}
|
||||
<td class="text-center col-lg-6"><img class="ra-avatar" src="{{ corpstats.alliance_logo }}">
|
||||
<td class="text-center col-lg-6"><img class="ra-avatar" src="{{ corpstats.alliance.logo_url_128 }}">
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
@@ -70,6 +70,7 @@
|
||||
{% for alt in main.alts %}
|
||||
{% if forloop.first %}
|
||||
<tr>
|
||||
<th></th>
|
||||
<th class="text-center">{% trans "Character" %}</th>
|
||||
<th class="text-center">{% trans "Corporation" %}</th>
|
||||
<th class="text-center">{% trans "Alliance" %}</th>
|
||||
@@ -77,10 +78,15 @@
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td class="text-center">{{ alt.character_name }}</td>
|
||||
<td class="text-center">{{ alt.corporation_name }}</td>
|
||||
<td class="text-center">{{ alt.alliance_name }}</td>
|
||||
<td class="text-center">
|
||||
<td class="text-center" style="width:5%">
|
||||
<div class="thumbnail" style="border: 0 none; box-shadow: none; background: transparent;">
|
||||
<img src="https://image.eveonline.com/Character/{{ alt.character_id }}_32.jpg" class="img-circle">
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center" style="width:30%">{{ alt.character_name }}</td>
|
||||
<td class="text-center" style="width:30%">{{ alt.corporation_name }}</td>
|
||||
<td class="text-center" style="width:30%">{{ alt.alliance_name }}</td>
|
||||
<td class="text-center" style="width:5%">
|
||||
<a href="https://zkillboard.com/character/{{ alt.character_id }}/"
|
||||
class="label label-danger" target="_blank">
|
||||
{% trans "Killboard" %}
|
||||
@@ -175,9 +181,25 @@
|
||||
{% endblock %}
|
||||
{% block extra_script %}
|
||||
$(document).ready(function(){
|
||||
$('#table-mains').DataTable();
|
||||
$('#table-members').DataTable();
|
||||
$('#table-unregistered').DataTable();
|
||||
$('#table-mains').DataTable({
|
||||
"columnDefs": [
|
||||
{ "sortable": false, "targets": [1] },
|
||||
],
|
||||
});
|
||||
$('#table-members').DataTable({
|
||||
"columnDefs": [
|
||||
{ "searchable": false, "targets": [0, 2] },
|
||||
{ "sortable": false, "targets": [0, 2] },
|
||||
],
|
||||
"order": [[ 1, "asc" ]],
|
||||
});
|
||||
$('#table-unregistered').DataTable({
|
||||
"columnDefs": [
|
||||
{ "searchable": false, "targets": [0, 2] },
|
||||
{ "sortable": false, "targets": [0, 2] },
|
||||
],
|
||||
"order": [[ 1, "asc" ]],
|
||||
});
|
||||
|
||||
});
|
||||
{% endblock %}
|
||||
@@ -35,6 +35,15 @@ class EveAllianceInfo(models.Model):
|
||||
def __str__(self):
|
||||
return self.alliance_name
|
||||
|
||||
def logo_url(self, size=32):
|
||||
return "https://image.eveonline.com/Alliance/%s_%s.png" % (self.alliance_id, size)
|
||||
|
||||
def __getattr__(self, item):
|
||||
if item.startswith('logo_url_'):
|
||||
size = item.strip('logo_url_')
|
||||
return self.logo_url(size)
|
||||
return self.__getattribute__(item)
|
||||
|
||||
|
||||
class EveCorporationInfo(models.Model):
|
||||
corporation_id = models.CharField(max_length=254, unique=True)
|
||||
@@ -60,6 +69,15 @@ class EveCorporationInfo(models.Model):
|
||||
def __str__(self):
|
||||
return self.corporation_name
|
||||
|
||||
def logo_url(self, size=32):
|
||||
return "https://image.eveonline.com/Corporation/%s_%s.png" % (self.corporation_id, size)
|
||||
|
||||
def __getattr__(self, item):
|
||||
if item.startswith('logo_url_'):
|
||||
size = item.strip('logo_url_')
|
||||
return self.logo_url(size)
|
||||
return self.__getattribute__(item)
|
||||
|
||||
|
||||
class EveCharacter(models.Model):
|
||||
character_id = models.CharField(max_length=254, unique=True)
|
||||
@@ -107,3 +125,12 @@ class EveCharacter(models.Model):
|
||||
|
||||
def __str__(self):
|
||||
return self.character_name
|
||||
|
||||
def portrait_url(self, size=32):
|
||||
return "https://image.eveonline.com/Character/%s_%s.jpg" % (self.character_id, size)
|
||||
|
||||
def __getattr__(self, item):
|
||||
if item.startswith('portrait_url_'):
|
||||
size = item.strip('portrait_url_')
|
||||
return self.portrait_url(size)
|
||||
return self.__getattribute__(item)
|
||||
|
||||
@@ -2,11 +2,9 @@
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from allianceauth.optimer.models import OpTimer
|
||||
|
||||
|
||||
class FatlinkForm(forms.Form):
|
||||
fatname = forms.CharField(label=_('Name of fat-link'), required=True)
|
||||
fleet = forms.CharField(label=_("Fleet Name"), max_length=50)
|
||||
duration = forms.IntegerField(label=_("Duration of fat-link"), required=True, initial=30, min_value=1,
|
||||
max_value=2147483647)
|
||||
fleet = forms.ModelChoiceField(label=_("Fleet"), queryset=OpTimer.objects.all().order_by('operation_name'))
|
||||
max_value=2147483647, help_text=_('minutes'))
|
||||
|
||||
|
||||
@@ -2,12 +2,10 @@
|
||||
# Generated by Django 1.10.1 on 2016-09-05 21:39
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import datetime
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
from django.utils.timezone import utc
|
||||
from django.utils import timezone
|
||||
|
||||
import allianceauth.fleetactivitytracking.models
|
||||
|
||||
@@ -36,9 +34,9 @@ class Migration(migrations.Migration):
|
||||
name='Fatlink',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('fatdatetime', models.DateTimeField(default=datetime.datetime(2016, 9, 5, 21, 39, 17, 307954, tzinfo=utc))),
|
||||
('fatdatetime', models.DateTimeField(default=timezone.now)),
|
||||
('duration', models.PositiveIntegerField()),
|
||||
('fleet', models.CharField(default=b'', max_length=254)),
|
||||
('fleet', models.CharField(default='', max_length=254)),
|
||||
('name', models.CharField(max_length=254)),
|
||||
('hash', models.CharField(max_length=254, unique=True)),
|
||||
('creator', models.ForeignKey(on_delete=models.SET(
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Generated by Django 2.0.2 on 2018-02-28 18:00
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fleetactivitytracking', '0004_make_strings_more_stringy'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='fatlink',
|
||||
name='name',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='fatlink',
|
||||
name='fleet',
|
||||
field=models.CharField(max_length=254),
|
||||
),
|
||||
]
|
||||
@@ -12,13 +12,12 @@ def get_sentinel_user():
|
||||
class Fatlink(models.Model):
|
||||
fatdatetime = models.DateTimeField(default=timezone.now)
|
||||
duration = models.PositiveIntegerField()
|
||||
fleet = models.CharField(max_length=254, default="")
|
||||
name = models.CharField(max_length=254)
|
||||
fleet = models.CharField(max_length=254)
|
||||
hash = models.CharField(max_length=254, unique=True)
|
||||
creator = models.ForeignKey(User, on_delete=models.SET(get_sentinel_user))
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
return self.fleet
|
||||
|
||||
|
||||
class Fat(models.Model):
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</div>
|
||||
<div class="col-lg-10 col-sm-2">
|
||||
<div class="alert alert-danger" role="alert">{% trans "Character not registered!" %}</div>
|
||||
{% trans "This character is not part of any registered API-key. You must go to" %} <a href=" {% url 'auth_api_key_management' %}">{% trans "API key management</a> and add an API with the character on before being able to click fleet attendance links." %}
|
||||
{% trans "This character is not associated with an auth account." %} <a href=" {% url 'authentication:add_character' %}">{% trans "Add it here" %}</a> {% trans "before attempting to click fleet attendance links." %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
<h1 class="page-header text-center">{% blocktrans %}Participation data statistics for {{ month }}, {{ year }}{% endblocktrans %}
|
||||
{% if char_id %}
|
||||
<div class="text-right">
|
||||
<a href="{% url 'fatlink:user_statistics_month' char_id previous_month|date:"Y" previous_month|date:"m" %}" class="btn btn-info">{% trans "Previous month" %}</a>
|
||||
<a href="{% url 'fatlink:user_statistics_month' char_id next_month|date:"Y" next_month|date:"m" %}" class="btn btn-info">{% trans "Next month" %}</a>
|
||||
<a href="{% url 'fatlink:user_statistics_month' char_id previous_month|date:'Y' previous_month|date:'m' %}" class="btn btn-info">{% trans "Previous month" %}</a>
|
||||
<a href="{% url 'fatlink:user_statistics_month' char_id next_month|date:'Y' next_month|date:'m' %}" class="btn btn-info">{% trans "Next month" %}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</h1>
|
||||
<h2>{% blocktrans %}{{ user }} has collected {{ n_fats }} links this month.{% endblocktrans %}</h2>
|
||||
<h2>{% blocktrans %}{{ user }} has collected {{ n_fats }} link{{ n_fats|pluralize }} this month.{% endblocktrans %}</h2>
|
||||
<table class="table table-responsive">
|
||||
<tr>
|
||||
<th class="col-md-2 text-center">{% trans "Ship" %}</th>
|
||||
@@ -29,26 +29,24 @@
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% if created_fats %}
|
||||
<h2>{% blocktrans %}{{ user }} has created {{ n_created_fats }} links this month.{% endblocktrans %}</h2>
|
||||
<h2>{% blocktrans %}{{ user }} has created {{ n_created_fats }} link{{ n_created_fats|pluralize }} this month.{% endblocktrans %}</h2>
|
||||
{% if created_fats %}
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th class="text-center">{% trans "Name" %}</th>
|
||||
<th class="text-center">{% trans "Creator" %}</th>
|
||||
<th class="text-center">{% trans "Fleet" %}</th>
|
||||
<th class="text-center">{% trans "Creator" %}</th>
|
||||
<th class="text-center">{% trans "Eve Time" %}</th>
|
||||
<th class="text-center">{% trans "Duration" %}</th>
|
||||
<th class="text-center">{% trans "Edit" %}</th>
|
||||
</tr>
|
||||
{% for link in created_fats %}
|
||||
<tr>
|
||||
<td class="text-center"><a href="{% url 'auth_click_fatlink_view' %}{{ link.hash }}/{{ link.name }}">{{ link.name }}</a></td>
|
||||
<td class="text-center"><a href="{% url 'fatlink:click' link.hash %}" class="label label-primary">{{ link.fleet }}</a></td>
|
||||
<td class="text-center">{{ link.creator.username }}</td>
|
||||
<td class="text-center">{{ link.fleet }}</td>
|
||||
<td class="text-center">{{ link.fatdatetime }}</td>
|
||||
<td class="text-center">{{ link.duration }}</td>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'auth_modify_fatlink_view' %}{{ link.hash }}/{{ link.name }}">
|
||||
<a href="{% url 'fatlink:modify' link.hash %}">
|
||||
<button type="button" class="btn btn-info"><span
|
||||
class="glyphicon glyphicon-edit"></span></button>
|
||||
</a>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
{% if fats %}
|
||||
<table class="table table-responsive">
|
||||
<tr>
|
||||
<th class="text-center">{% trans "fatname" %}</th>
|
||||
<th class="text-center">{% trans "Fleet" %}</th>
|
||||
<th class="text-center">{% trans "Character" %}</th>
|
||||
<th class="text-center">{% trans "System" %}</th>
|
||||
<th class="text-center">{% trans "Ship" %}</th>
|
||||
@@ -32,7 +32,7 @@
|
||||
</tr>
|
||||
{% for fat in fats %}
|
||||
<tr>
|
||||
<td class="text-center">{{ fat.fatlink.name }}</td>
|
||||
<td class="text-center">{{ fat.fatlink.fleet }}</td>
|
||||
<td class="text-center">{{ fat.character.character_name }}</td>
|
||||
{% if fat.station != "No Station" %}
|
||||
<td class="text-center">{% blocktrans %}Docked in {% endblocktrans %}{{ fat.system }}</td>
|
||||
@@ -79,13 +79,13 @@
|
||||
</tr>
|
||||
{% for link in fatlinks %}
|
||||
<tr>
|
||||
<td class="text-center"><a href="{% url 'fatlink:click_fatlink' %}{{ link.hash }}/{{ link.name }}">{{ link.name }}</a></td>
|
||||
<td class="text-center"><a href="{% url 'fatlink:click' link.hash %}" class="label label-primary">{{ link.fleet }}</a></td>
|
||||
<td class="text-center">{{ link.creator.username }}</td>
|
||||
<td class="text-center">{{ link.fleet }}</td>
|
||||
<td class="text-center">{{ link.fatdatetime }}</td>
|
||||
<td class="text-center">{{ link.duration }}</td>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'fatlink:modify' %}{{ link.hash }}/{{ link.name }}" class="btn btn-info">
|
||||
<a href="{% url 'fatlink:modify' link.hash %}" class="btn btn-info">
|
||||
<span class="glyphicon glyphicon-edit"></span>
|
||||
</a>
|
||||
</td>
|
||||
|
||||
@@ -25,10 +25,6 @@ urlpatterns = [
|
||||
views.fatlink_monthly_personal_statistics_view,
|
||||
name='user_statistics_month'),
|
||||
url(r'^create/$', views.create_fatlink_view, name='create'),
|
||||
url(r'^modify/$', views.modify_fatlink_view, name='modify'),
|
||||
url(r'^modify/(?P<hash>[a-zA-Z0-9_-]+)/([a-z0-9_-]+)$',
|
||||
views.modify_fatlink_view),
|
||||
url(r'^link/$', views.fatlink_view, name='click_fatlink'),
|
||||
url(r'^link/(?P<hash>[a-zA-Z0-9]+)/(?P<fatname>[a-z0-9_-]+)/$',
|
||||
views.click_fatlink_view),
|
||||
url(r'^modify/(?P<fat_hash>[a-zA-Z0-9_-]+)/$', views.modify_fatlink_view, name='modify'),
|
||||
url(r'^link/(?P<fat_hash>[a-zA-Z0-9]+)/$', views.click_fatlink_view, name='click'),
|
||||
]
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import datetime
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import string
|
||||
|
||||
from allianceauth.authentication.models import CharacterOwnership
|
||||
from django.contrib import messages
|
||||
@@ -17,7 +15,7 @@ from esi.decorators import token_required
|
||||
from allianceauth.eveonline.providers import provider
|
||||
from .forms import FatlinkForm
|
||||
from .models import Fatlink, Fat
|
||||
from slugify import slugify
|
||||
from django.utils.crypto import get_random_string
|
||||
|
||||
from allianceauth.eveonline.models import EveAllianceInfo
|
||||
from allianceauth.eveonline.models import EveCharacter
|
||||
@@ -181,7 +179,7 @@ def fatlink_personal_statistics_view(request, year=datetime.date.today().year):
|
||||
|
||||
personal_fats = Fat.objects.select_related('fatlink').filter(user=user).order_by('id')
|
||||
|
||||
monthlystats = [0 for month in range(1, 13)]
|
||||
monthlystats = [0 for i in range(1, 13)]
|
||||
|
||||
for fat in personal_fats:
|
||||
fatdate = fat.fatlink.fatdatetime
|
||||
@@ -236,8 +234,8 @@ def fatlink_monthly_personal_statistics_view(request, year, month, char_id=None)
|
||||
@login_required
|
||||
@token_required(
|
||||
scopes=['esi-location.read_location.v1', 'esi-location.read_ship_type.v1', 'esi-universe.read_structures.v1'])
|
||||
def click_fatlink_view(request, token, hash, fatname):
|
||||
fatlink = get_object_or_404(Fatlink, hash=hash, name=fatname)
|
||||
def click_fatlink_view(request, token, fat_hash=None):
|
||||
fatlink = get_object_or_404(Fatlink, hash=fat_hash)
|
||||
|
||||
if (timezone.now() - fatlink.fatdatetime) < datetime.timedelta(seconds=(fatlink.duration * 60)):
|
||||
|
||||
@@ -298,12 +296,11 @@ def create_fatlink_view(request):
|
||||
logger.debug("Submitting fleetactivitytracking by user %s" % request.user)
|
||||
if form.is_valid():
|
||||
fatlink = Fatlink()
|
||||
fatlink.name = slugify(form.cleaned_data["fatname"])
|
||||
fatlink.fleet = form.cleaned_data["fleet"]
|
||||
fatlink.duration = form.cleaned_data["duration"]
|
||||
fatlink.fatdatetime = timezone.now()
|
||||
fatlink.creator = request.user
|
||||
fatlink.hash = ''.join(random.choice(string.ascii_letters + string.digits) for i in range(10))
|
||||
fatlink.hash = get_random_string(length=15)
|
||||
try:
|
||||
fatlink.full_clean()
|
||||
fatlink.save()
|
||||
@@ -331,25 +328,19 @@ def create_fatlink_view(request):
|
||||
|
||||
@login_required
|
||||
@permission_required('auth.fleetactivitytracking')
|
||||
def modify_fatlink_view(request, hash=""):
|
||||
def modify_fatlink_view(request, fat_hash=None):
|
||||
logger.debug("modify_fatlink_view called by user %s" % request.user)
|
||||
if not hash:
|
||||
return redirect('fatlink:view')
|
||||
|
||||
try:
|
||||
fatlink = Fatlink.objects.get(hash=hash)
|
||||
except Fatlink.DoesNotExist:
|
||||
raise Http404
|
||||
fatlink = get_object_or_404(Fatlink, hash=fat_hash)
|
||||
|
||||
if request.GET.get('removechar', None):
|
||||
character_id = request.GET.get('removechar')
|
||||
character = EveCharacter.objects.get(character_id=character_id)
|
||||
logger.debug("Removing character %s from fleetactivitytracking %s" % (character.character_name, fatlink.name))
|
||||
logger.debug("Removing character %s from fleetactivitytracking %s" % (character.character_name, fatlink))
|
||||
|
||||
Fat.objects.filter(fatlink=fatlink).filter(character=character).delete()
|
||||
|
||||
if request.GET.get('deletefat', None):
|
||||
logger.debug("Removing fleetactivitytracking %s" % fatlink.name)
|
||||
logger.debug("Removing fleetactivitytracking %s" % fatlink)
|
||||
fatlink.delete()
|
||||
return redirect('fatlink:view')
|
||||
|
||||
|
||||
@@ -1,28 +1,68 @@
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.models import Group
|
||||
|
||||
from django.contrib.auth.models import Group as BaseGroup
|
||||
from django.db.models.signals import pre_save, post_save, pre_delete, post_delete, m2m_changed
|
||||
from django.dispatch import receiver
|
||||
from .models import AuthGroup
|
||||
from .models import GroupRequest
|
||||
|
||||
|
||||
class AuthGroupAdmin(admin.ModelAdmin):
|
||||
"""
|
||||
Admin model for AuthGroup
|
||||
"""
|
||||
class AuthGroupInlineAdmin(admin.StackedInline):
|
||||
model = AuthGroup
|
||||
filter_horizontal = ('group_leaders',)
|
||||
fields = ('description', 'group_leaders', 'internal', 'hidden', 'open', 'public')
|
||||
verbose_name_plural = 'Auth Settings'
|
||||
verbose_name = ''
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
return request.user.has_perm('auth.change_group')
|
||||
|
||||
|
||||
class ProxyGroup(Group):
|
||||
class GroupAdmin(admin.ModelAdmin):
|
||||
filter_horizontal = ('permissions',)
|
||||
inlines = (AuthGroupInlineAdmin,)
|
||||
|
||||
|
||||
class Group(BaseGroup):
|
||||
class Meta:
|
||||
proxy = True
|
||||
verbose_name = Group._meta.verbose_name
|
||||
verbose_name_plural = Group._meta.verbose_name_plural
|
||||
verbose_name = BaseGroup._meta.verbose_name
|
||||
verbose_name_plural = BaseGroup._meta.verbose_name_plural
|
||||
|
||||
try:
|
||||
admin.site.unregister(Group)
|
||||
admin.site.unregister(BaseGroup)
|
||||
finally:
|
||||
admin.site.register(ProxyGroup)
|
||||
admin.site.register(Group, GroupAdmin)
|
||||
|
||||
|
||||
admin.site.register(GroupRequest)
|
||||
admin.site.register(AuthGroup, AuthGroupAdmin)
|
||||
|
||||
|
||||
@receiver(pre_save, sender=Group)
|
||||
def redirect_pre_save(sender, signal=None, *args, **kwargs):
|
||||
pre_save.send(BaseGroup, *args, **kwargs)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Group)
|
||||
def redirect_post_save(sender, signal=None, *args, **kwargs):
|
||||
post_save.send(BaseGroup, *args, **kwargs)
|
||||
|
||||
|
||||
@receiver(pre_delete, sender=Group)
|
||||
def redirect_pre_delete(sender, signal=None, *args, **kwargs):
|
||||
pre_delete.send(BaseGroup, *args, **kwargs)
|
||||
|
||||
|
||||
@receiver(post_delete, sender=Group)
|
||||
def redirect_post_delete(sender, signal=None, *args, **kwargs):
|
||||
post_delete.send(BaseGroup, *args, **kwargs)
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=Group.permissions.through)
|
||||
def redirect_m2m_changed_permissions(sender, signal=None, *args, **kwargs):
|
||||
m2m_changed.send(BaseGroup, *args, **kwargs)
|
||||
@@ -4,3 +4,4 @@ from django.apps import AppConfig
|
||||
class GroupManagementConfig(AppConfig):
|
||||
name = 'allianceauth.groupmanagement'
|
||||
label = 'groupmanagement'
|
||||
verbose_name = 'Group Management'
|
||||
|
||||
@@ -15,7 +15,7 @@ class Migration(migrations.Migration):
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ProxyGroup',
|
||||
name='Group',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.10 on 2018-02-23 23:09
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def delete_permissions(apps, schema_editor):
|
||||
AuthGroup = apps.get_model('groupmanagement', 'AuthGroup')
|
||||
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||
Permission = apps.get_model('auth', 'Permission')
|
||||
ct = ContentType.objects.get_for_model(AuthGroup)
|
||||
Permission.objects.filter(content_type=ct).delete()
|
||||
|
||||
|
||||
def recreate_permissions(apps, schema_editor):
|
||||
AuthGroup = apps.get_model('groupmanagement', 'AuthGroup')
|
||||
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||
Permission = apps.get_model('auth', 'Permission')
|
||||
ct = ContentType.objects.get_for_model(AuthGroup)
|
||||
Permission.objects.create(content_type=ct, name='Can add auth group', codename='add_authgroup')
|
||||
Permission.objects.create(content_type=ct, name='Can delete auth group', codename='delete_authgroup')
|
||||
Permission.objects.create(content_type=ct, name='Can change auth group', codename='change_authgroup')
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('groupmanagement', '0007_on_delete'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='authgroup',
|
||||
options={'default_permissions': (), 'permissions': (('request_groups', 'Can request non-public groups'),)},
|
||||
),
|
||||
migrations.RunPython(delete_permissions, recreate_permissions)
|
||||
]
|
||||
@@ -74,6 +74,7 @@ class AuthGroup(models.Model):
|
||||
permissions = (
|
||||
("request_groups", u"Can request non-public groups"),
|
||||
)
|
||||
default_permissions = tuple()
|
||||
|
||||
|
||||
@receiver(post_save, sender=Group)
|
||||
|
||||
@@ -154,6 +154,12 @@
|
||||
class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-eye-open"></span>
|
||||
</a>
|
||||
{% if perms.hrapplications.delete_application %}
|
||||
<a href="(% url 'hrapplications:remove' app.id %}"
|
||||
class="btn btn-danger">
|
||||
<span class="glyphicon glyphicon-remove"></span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
@@ -67,7 +67,7 @@ def hr_application_create_view(request, form_id=None):
|
||||
""))
|
||||
response.save()
|
||||
logger.info("%s created %s" % (request.user, application))
|
||||
return redirect('hrapplications:view')
|
||||
return redirect('hrapplications:personal_view', application.id)
|
||||
else:
|
||||
questions = app_form.questions.all()
|
||||
return render(request, 'hrapplications/create.html',
|
||||
@@ -95,7 +95,7 @@ def hr_application_personal_view(request, app_id):
|
||||
return render(request, 'hrapplications/view.html', context=context)
|
||||
else:
|
||||
logger.warn("User %s not authorized to view %s" % (request.user, app))
|
||||
return redirect('hrapplications:view')
|
||||
return redirect('hrapplications:personal_view')
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -110,7 +110,7 @@ def hr_application_personal_removal(request, app_id):
|
||||
logger.warn("User %s attempting to delete reviewed app %s" % (request.user, app))
|
||||
else:
|
||||
logger.warn("User %s not authorized to delete %s" % (request.user, app))
|
||||
return redirect('hrapplications:view')
|
||||
return redirect('hrapplications:index')
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -158,7 +158,7 @@ def hr_application_remove(request, app_id):
|
||||
logger.info("User %s deleting %s" % (request.user, app))
|
||||
app.delete()
|
||||
notify(app.user, "Application Deleted", message="Your application to %s was deleted." % app.form.corp)
|
||||
return redirect('hrapplications:view')
|
||||
return redirect('hrapplications:index')
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -175,7 +175,7 @@ def hr_application_approve(request, app_id):
|
||||
level="success")
|
||||
else:
|
||||
logger.warn("User %s not authorized to approve %s" % (request.user, app))
|
||||
return redirect('hrapplications:view')
|
||||
return redirect('hrapplications:index')
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -192,7 +192,7 @@ def hr_application_reject(request, app_id):
|
||||
level="danger")
|
||||
else:
|
||||
logger.warn("User %s not authorized to reject %s" % (request.user, app))
|
||||
return redirect('hrapplications:view')
|
||||
return redirect('hrapplications:index')
|
||||
|
||||
|
||||
@login_required
|
||||
|
||||
@@ -8,6 +8,7 @@ from allianceauth.tests.auth_utils import AuthUtils
|
||||
class PermissionsToolViewsTestCase(WebTest):
|
||||
def setUp(self):
|
||||
self.member = AuthUtils.create_member('auth_member')
|
||||
AuthUtils.add_main_character(self.member, 'test character', '1234', '2345', 'test corp', 'testc')
|
||||
self.member.email = 'auth_member@example.com'
|
||||
self.member.save()
|
||||
self.none_user = AuthUtils.create_user('none_user', disconnect_signals=True)
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -10,7 +10,7 @@ app = Celery('{{ project_name }}')
|
||||
|
||||
# Using a string here means the worker don't have to serialize
|
||||
# the configuration object to child processes.
|
||||
app.config_from_object('django.conf:settings', namespace='CELERY')
|
||||
app.config_from_object('django.conf:settings')
|
||||
|
||||
# Load task modules from all registered Django app configs.
|
||||
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
# -*- coding: UTF-8 -*-
|
||||
"""
|
||||
Django settings for alliance_auth project.
|
||||
DO NOT EDIT THIS FILE
|
||||
|
||||
Generated by 'django-admin startproject' using Django 1.10.1.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/1.10/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/1.10/ref/settings/
|
||||
This settings file contains everything needed for Alliance Auth projects to function.
|
||||
It gets overwritten by the 'allianceauth update' command.
|
||||
If you wish to make changes, overload the setting in your project's settings file (local.py).
|
||||
"""
|
||||
|
||||
import os
|
||||
@@ -17,7 +13,6 @@ from django.contrib import messages
|
||||
from celery.schedules import crontab
|
||||
|
||||
INSTALLED_APPS = [
|
||||
# Core apps - required to function
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
@@ -38,8 +33,10 @@ INSTALLED_APPS = [
|
||||
'allianceauth.thirdparty.navhelper',
|
||||
]
|
||||
|
||||
SECRET_KEY = "wow I'm a really bad default secret key"
|
||||
|
||||
# Celery configuration
|
||||
CELERY_BROKER_URL = 'redis://localhost:6379/0'
|
||||
BROKER_URL = 'redis://localhost:6379/0'
|
||||
CELERYBEAT_SCHEDULER = "django_celery_beat.schedulers.DatabaseScheduler"
|
||||
CELERYBEAT_SCHEDULE = {
|
||||
'esi_cleanup_callbackredirect': {
|
||||
@@ -90,7 +87,7 @@ LANGUAGES = (
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'DIRS': [os.path.join(PROJECT_DIR, 'templates')],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
@@ -148,8 +145,11 @@ USE_TZ = True
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/1.10/howto/static-files/
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
STATICFILES_DIRS = [
|
||||
os.path.join(PROJECT_DIR, 'static'),
|
||||
]
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
|
||||
|
||||
# Bootstrap messaging css workaround
|
||||
MESSAGE_TAGS = {
|
||||
@@ -178,40 +178,22 @@ DATABASES = {
|
||||
|
||||
SITE_NAME = 'Alliance Auth'
|
||||
|
||||
#################
|
||||
# Login Settings
|
||||
#################
|
||||
# LOGIN_REDIRECT_URL - default destination when logging in if no redirect specified
|
||||
# LOGOUT_REDIRECT_URL - destination after logging out
|
||||
LOGIN_URL = 'auth_login_user' # view that handles login logic
|
||||
|
||||
LOGIN_REDIRECT_URL = 'authentication:dashboard' # default destination when logging in if no redirect specified
|
||||
LOGOUT_REDIRECT_URL = 'authentication:dashboard' # destination after logging out
|
||||
# Both of these redirects accept values as per the django redirect shortcut
|
||||
# https://docs.djangoproject.com/en/1.11/topics/http/shortcuts/#redirect
|
||||
# - url names eg 'authentication:dashboard'
|
||||
# - relative urls eg '/dashboard'
|
||||
# - absolute urls eg 'http://example.com/dashboard'
|
||||
# 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_URL = 'auth_login_user'
|
||||
LOGIN_REDIRECT_URL = 'authentication:dashboard'
|
||||
LOGOUT_REDIRECT_URL = 'authentication:dashboard'
|
||||
LOGIN_TOKEN_SCOPES = ['esi-characters.read_opportunities.v1']
|
||||
|
||||
# scopes required on new tokens when logging in. Cannot be blank.
|
||||
LOGIN_TOKEN_SCOPES = ['publicData']
|
||||
|
||||
# number of days email verification links are valid for
|
||||
ACCOUNT_ACTIVATION_DAYS = 1
|
||||
|
||||
#####################################################
|
||||
##
|
||||
## Logging Configuration
|
||||
##
|
||||
#####################################################
|
||||
# Set log_file and console level to desired state:
|
||||
# DEBUG - basically stack trace, explains every step
|
||||
# INFO - model creation, deletion, updates, etc
|
||||
# WARN - unexpected function outcomes that do not impact user
|
||||
# ERROR - unexcpeted function outcomes which prevent user from achieving desired outcome
|
||||
# EXCEPTION - something critical went wrong, unhandled
|
||||
#####################################
|
||||
# Recommended level for log_file is INFO, console is DEBUG
|
||||
# Change log level of individual apps below to narrow your debugging
|
||||
#####################################
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
|
||||
@@ -1,50 +1,28 @@
|
||||
# Every setting in base.py can be overloaded by redefining it here.
|
||||
from .base import *
|
||||
|
||||
# These are required for Django to function properly
|
||||
# These are required for Django to function properly. Don't touch.
|
||||
ROOT_URLCONF = '{{ project_name }}.urls'
|
||||
WSGI_APPLICATION = '{{ project_name }}.wsgi.application'
|
||||
STATICFILES_DIRS = [
|
||||
os.path.join(PROJECT_DIR, 'static'),
|
||||
]
|
||||
STATIC_ROOT = "/var/www/{{ project_name }}/static/"
|
||||
TEMPLATES[0]['DIRS'] += [os.path.join(PROJECT_DIR, 'templates')]
|
||||
SECRET_KEY = '{{ secret_key }}'
|
||||
|
||||
#######################################
|
||||
# Site Name #
|
||||
#######################################
|
||||
# Change this to change the name of the
|
||||
# auth site displayed in page titles
|
||||
# and the site header.
|
||||
#######################################
|
||||
# This is where css/images will be placed for your webserver to read
|
||||
STATIC_ROOT = "/var/www/{{ project_name }}/static/"
|
||||
|
||||
# Change this to change the name of the auth site displayed
|
||||
# in page titles and the site header.
|
||||
SITE_NAME = '{{ project_name }}'
|
||||
|
||||
#######################################
|
||||
# Debug Mode #
|
||||
#######################################
|
||||
# Change this to enable/disable debug
|
||||
# mode, which displays useful error
|
||||
# messages but can leak sensitive data.
|
||||
#######################################
|
||||
# Change this to enable/disable debug mode, which displays
|
||||
# useful error messages but can leak sensitive data.
|
||||
DEBUG = False
|
||||
|
||||
#######################################
|
||||
# Additional Applications #
|
||||
#######################################
|
||||
# Add any additional apps to this list.
|
||||
#######################################
|
||||
INSTALLED_APPS += [
|
||||
|
||||
]
|
||||
|
||||
#######################################
|
||||
# Database Settings #
|
||||
#######################################
|
||||
# Uncomment and change the database name
|
||||
# and credentials to use MySQL/MariaDB.
|
||||
# Leave commented to use sqlite3
|
||||
#######################################
|
||||
"""
|
||||
# Enter credentials to use MySQL/MariaDB. Comment out to use sqlite3
|
||||
DATABASES['default'] = {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'NAME': 'alliance_auth',
|
||||
@@ -53,33 +31,20 @@ DATABASES['default'] = {
|
||||
'HOST': '127.0.0.1',
|
||||
'PORT': '3306',
|
||||
}
|
||||
"""
|
||||
|
||||
#######################################
|
||||
# SSO Settings #
|
||||
#######################################
|
||||
# Register an application at
|
||||
# https://developers.eveonline.com
|
||||
# and fill out these settings.
|
||||
# Be sure to set the callback URL to
|
||||
# https://example.com/sso/callback
|
||||
# substituting your domain for example.com
|
||||
#######################################
|
||||
# Register an application at https://developers.eveonline.com for Authentication
|
||||
# & API Access and fill out these settings. Be sure to set the callback URL
|
||||
# to https://example.com/sso/callback substituting your domain for example.com
|
||||
# Logging in to auth requires the publicData scope (can be overridden through the
|
||||
# LOGIN_TOKEN_SCOPES setting). Other apps may require more (see their docs).
|
||||
ESI_SSO_CLIENT_ID = ''
|
||||
ESI_SSO_CLIENT_SECRET = ''
|
||||
ESI_SSO_CALLBACK_URL = ''
|
||||
|
||||
#######################################
|
||||
# Email Settings #
|
||||
#######################################
|
||||
# Alliance Auth validates emails before
|
||||
# new users can log in.
|
||||
# It's recommended to use a free service
|
||||
# like SparkPost or Mailgun to send email.
|
||||
# Emails are validated before new users can log in.
|
||||
# It's recommended to use a free service like SparkPost or Mailgun to send email.
|
||||
# https://www.sparkpost.com/docs/integrations/django/
|
||||
# Set the default from email to something
|
||||
# like 'noreply@example.com'
|
||||
#######################################
|
||||
# Set the default from email to something like 'noreply@example.com'
|
||||
EMAIL_HOST = ''
|
||||
EMAIL_PORT = 587
|
||||
EMAIL_HOST_USER = ''
|
||||
|
||||
@@ -17,6 +17,11 @@ class NameFormatConfigForm(forms.ModelForm):
|
||||
|
||||
class NameFormatConfigAdmin(admin.ModelAdmin):
|
||||
form = NameFormatConfigForm
|
||||
list_display = ('service_name', 'get_state_display_string')
|
||||
|
||||
def get_state_display_string(self, obj):
|
||||
return ', '.join([state.name for state in obj.states.all()])
|
||||
get_state_display_string.short_description = 'States'
|
||||
|
||||
|
||||
admin.site.register(NameFormatConfig, NameFormatConfigAdmin)
|
||||
|
||||
@@ -9,5 +9,5 @@ class Command(BaseCommand):
|
||||
|
||||
def handle(self, *args, **options):
|
||||
for u in User.objects.all():
|
||||
validate_services(u)
|
||||
validate_services(u.pk)
|
||||
self.stdout.write(self.style.SUCCESS('Verified all user service accounts.'))
|
||||
|
||||
@@ -11,6 +11,7 @@ class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('services', '0001_squashed_0003_delete_groupcache'),
|
||||
('authentication', '0015_user_profiles'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import requests
|
||||
import json
|
||||
import re
|
||||
import math
|
||||
from django.conf import settings
|
||||
from requests_oauthlib import OAuth2Session
|
||||
@@ -24,8 +22,8 @@ Previously all we asked for was permission to kick members, manage roles, and ma
|
||||
Users have reported weird unauthorized errors we don't understand. So now we ask for full server admin.
|
||||
It's almost fixed the problem.
|
||||
"""
|
||||
# kick members, manage roles, manage nicknames
|
||||
# BOT_PERMISSIONS = 0x00000002 + 0x10000000 + 0x08000000
|
||||
# kick members, manage roles, manage nicknames, create instant invite
|
||||
# BOT_PERMISSIONS = 0x00000002 + 0x10000000 + 0x08000000 + 0x00000001
|
||||
BOT_PERMISSIONS = 0x00000008
|
||||
|
||||
# get user ID, accept invite
|
||||
@@ -109,7 +107,7 @@ def api_backoff(func):
|
||||
backoff_timer = datetime.datetime.strptime(existing_backoff, cache_time_format)
|
||||
if backoff_timer > datetime.datetime.utcnow():
|
||||
backoff_seconds = (backoff_timer - datetime.datetime.utcnow()).total_seconds()
|
||||
logger.debug("Still under backoff for {} seconds, backing off" % backoff_seconds)
|
||||
logger.debug("Still under backoff for %s seconds, backing off" % backoff_seconds)
|
||||
# Still under backoff
|
||||
raise PerformBackoff(
|
||||
retry_after=backoff_seconds,
|
||||
@@ -117,8 +115,7 @@ def api_backoff(func):
|
||||
global_ratelimit=bool(existing_global_backoff)
|
||||
)
|
||||
logger.debug("Calling API calling function")
|
||||
func(*args, **kwargs)
|
||||
break
|
||||
return func(*args, **kwargs)
|
||||
except requests.HTTPError as e:
|
||||
if e.response.status_code == 429:
|
||||
try:
|
||||
@@ -163,12 +160,11 @@ class DiscordOAuthManager:
|
||||
|
||||
@staticmethod
|
||||
def _sanitize_name(name):
|
||||
return re.sub('[^\w.-]', '', name)[:32]
|
||||
return name[:32]
|
||||
|
||||
@staticmethod
|
||||
def _sanitize_groupname(name):
|
||||
name = name.strip(' _')
|
||||
return DiscordOAuthManager._sanitize_name(name)
|
||||
def _sanitize_group_name(name):
|
||||
return name[:100]
|
||||
|
||||
@staticmethod
|
||||
def generate_bot_add_url():
|
||||
@@ -187,23 +183,33 @@ class DiscordOAuthManager:
|
||||
return token
|
||||
|
||||
@staticmethod
|
||||
def add_user(code):
|
||||
def add_user(code, groups, nickname=None):
|
||||
try:
|
||||
token = DiscordOAuthManager._process_callback_code(code)['access_token']
|
||||
logger.debug("Received token from OAuth")
|
||||
|
||||
custom_headers = {'accept': 'application/json', 'authorization': 'Bearer ' + token}
|
||||
path = DISCORD_URL + "/invites/" + str(settings.DISCORD_INVITE_CODE)
|
||||
r = requests.post(path, headers=custom_headers)
|
||||
logger.debug("Got status code %s after accepting Discord invite" % r.status_code)
|
||||
r.raise_for_status()
|
||||
|
||||
path = DISCORD_URL + "/users/@me"
|
||||
r = requests.get(path, headers=custom_headers)
|
||||
logger.debug("Got status code %s after retrieving Discord profile" % r.status_code)
|
||||
r.raise_for_status()
|
||||
|
||||
user_id = r.json()['id']
|
||||
|
||||
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/members/" + str(user_id)
|
||||
group_ids = [DiscordOAuthManager._group_name_to_id(DiscordOAuthManager._sanitize_group_name(g)) for g in
|
||||
groups]
|
||||
data = {
|
||||
'roles': group_ids,
|
||||
'access_token': token,
|
||||
}
|
||||
if nickname:
|
||||
data['nick'] = nickname
|
||||
custom_headers['authorization'] = 'Bot ' + settings.DISCORD_BOT_TOKEN
|
||||
r = requests.put(path, headers=custom_headers, json=data)
|
||||
logger.debug("Got status code %s after joining Discord server" % r.status_code)
|
||||
r.raise_for_status()
|
||||
|
||||
logger.info("Added Discord user ID %s to server." % user_id)
|
||||
return user_id
|
||||
except:
|
||||
@@ -211,6 +217,7 @@ class DiscordOAuthManager:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
@api_backoff
|
||||
def update_nickname(user_id, nickname):
|
||||
try:
|
||||
nickname = DiscordOAuthManager._sanitize_name(nickname)
|
||||
@@ -260,7 +267,7 @@ class DiscordOAuthManager:
|
||||
|
||||
@staticmethod
|
||||
def _group_name_to_id(name):
|
||||
name = DiscordOAuthManager._sanitize_groupname(name)
|
||||
name = DiscordOAuthManager._sanitize_group_name(name)
|
||||
|
||||
def get_or_make_role():
|
||||
groups = DiscordOAuthManager._get_groups()
|
||||
@@ -271,42 +278,36 @@ class DiscordOAuthManager:
|
||||
return cache.get_or_set(DiscordOAuthManager._generate_cache_role_key(name), get_or_make_role, GROUP_CACHE_MAX_AGE)
|
||||
|
||||
@staticmethod
|
||||
def __generate_role():
|
||||
def __generate_role(name, **kwargs):
|
||||
custom_headers = {'accept': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
||||
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/roles"
|
||||
r = requests.post(path, headers=custom_headers)
|
||||
data = {'name': name}
|
||||
data.update(kwargs)
|
||||
r = requests.post(path, headers=custom_headers, json=data)
|
||||
logger.debug("Received status code %s after generating new role." % r.status_code)
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
@staticmethod
|
||||
def __edit_role(role_id, name, color=0, hoist=True, permissions=36785152):
|
||||
def __edit_role(role_id, **kwargs):
|
||||
custom_headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
||||
data = {
|
||||
'color': color,
|
||||
'hoist': hoist,
|
||||
'name': name,
|
||||
'permissions': permissions,
|
||||
}
|
||||
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/roles/" + str(role_id)
|
||||
r = requests.patch(path, headers=custom_headers, data=json.dumps(data))
|
||||
r = requests.patch(path, headers=custom_headers, json=kwargs)
|
||||
logger.debug("Received status code %s after editing role id %s" % (r.status_code, role_id))
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
@staticmethod
|
||||
def _create_group(name):
|
||||
role = DiscordOAuthManager.__generate_role()
|
||||
return DiscordOAuthManager.__edit_role(role['id'], name)
|
||||
return DiscordOAuthManager.__generate_role(name)
|
||||
|
||||
@staticmethod
|
||||
@api_backoff
|
||||
def update_groups(user_id, groups):
|
||||
custom_headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
||||
group_ids = [DiscordOAuthManager._group_name_to_id(DiscordOAuthManager._sanitize_groupname(g)) for g in groups]
|
||||
group_ids = [DiscordOAuthManager._group_name_to_id(DiscordOAuthManager._sanitize_group_name(g)) for g in groups]
|
||||
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/members/" + str(user_id)
|
||||
data = {'roles': group_ids}
|
||||
r = requests.patch(path, headers=custom_headers, json=data)
|
||||
logger.debug("Received status code %s after setting user roles" % r.status_code)
|
||||
r.raise_for_status()
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from allianceauth.notifications import notify
|
||||
from celery import shared_task
|
||||
|
||||
from requests.exceptions import HTTPError
|
||||
from allianceauth.services.hooks import NameFormatter
|
||||
from .manager import DiscordOAuthManager, DiscordApiBackoff
|
||||
from .models import DiscordUser
|
||||
@@ -19,15 +19,16 @@ class DiscordTasks:
|
||||
|
||||
@classmethod
|
||||
def add_user(cls, user, code):
|
||||
user_id = DiscordOAuthManager.add_user(code)
|
||||
groups = DiscordTasks.get_groups(user)
|
||||
nickname = None
|
||||
if settings.DISCORD_SYNC_NAMES:
|
||||
nickname = DiscordTasks.get_nickname(user)
|
||||
user_id = DiscordOAuthManager.add_user(code, groups, nickname=nickname)
|
||||
if user_id:
|
||||
discord_user = DiscordUser()
|
||||
discord_user.user = user
|
||||
discord_user.uid = user_id
|
||||
discord_user.save()
|
||||
if settings.DISCORD_SYNC_NAMES:
|
||||
cls.update_nickname.delay(user.pk)
|
||||
cls.update_groups.delay(user.pk)
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -62,12 +63,7 @@ class DiscordTasks:
|
||||
user = User.objects.get(pk=pk)
|
||||
logger.debug("Updating discord groups for user %s" % user)
|
||||
if DiscordTasks.has_account(user):
|
||||
groups = []
|
||||
for group in user.groups.all():
|
||||
groups.append(str(group.name))
|
||||
if len(groups) == 0:
|
||||
logger.debug("No syncgroups found for user. Adding empty group.")
|
||||
groups.append('empty')
|
||||
groups = DiscordTasks.get_groups(user)
|
||||
logger.debug("Updating user %s discord groups to %s" % (user, groups))
|
||||
try:
|
||||
DiscordOAuthManager.update_groups(user.discord.uid, groups)
|
||||
@@ -75,6 +71,15 @@ class DiscordTasks:
|
||||
logger.info("Discord group sync API back off for %s, "
|
||||
"retrying in %s seconds" % (user, bo.retry_after_seconds))
|
||||
raise task_self.retry(countdown=bo.retry_after_seconds)
|
||||
except HTTPError as e:
|
||||
if e.response.status_code == 404:
|
||||
try:
|
||||
if e.response.json()['code'] == 10007:
|
||||
# user has left the server
|
||||
DiscordTasks.delete_user(user)
|
||||
return
|
||||
finally:
|
||||
raise e
|
||||
except Exception as e:
|
||||
if task_self:
|
||||
logger.exception("Discord group sync failed for %s, retrying in 10 mins" % user)
|
||||
@@ -95,7 +100,7 @@ class DiscordTasks:
|
||||
|
||||
@staticmethod
|
||||
@shared_task(bind=True, name='discord.update_nickname')
|
||||
def update_nickname(self, pk):
|
||||
def update_nickname(task_self, pk):
|
||||
user = User.objects.get(pk=pk)
|
||||
logger.debug("Updating discord nickname for user %s" % user)
|
||||
if DiscordTasks.has_account(user):
|
||||
@@ -104,10 +109,14 @@ class DiscordTasks:
|
||||
logger.debug("Updating user %s discord nickname to %s" % (user, character.character_name))
|
||||
try:
|
||||
DiscordOAuthManager.update_nickname(user.discord.uid, DiscordTasks.get_nickname(user))
|
||||
except DiscordApiBackoff as bo:
|
||||
logger.info("Discord nickname update API back off for %s, "
|
||||
"retrying in %s seconds" % (user, bo.retry_after_seconds))
|
||||
raise task_self.retry(countdown=bo.retry_after_seconds)
|
||||
except Exception as e:
|
||||
if self:
|
||||
if task_self:
|
||||
logger.exception("Discord nickname sync failed for %s, retrying in 10 mins" % user)
|
||||
raise self.retry(countdown=60 * 10)
|
||||
raise task_self.retry(countdown=60 * 10)
|
||||
else:
|
||||
# Rethrow
|
||||
raise e
|
||||
@@ -132,3 +141,7 @@ class DiscordTasks:
|
||||
def get_nickname(user):
|
||||
from .auth_hooks import DiscordService
|
||||
return NameFormatter(DiscordService(), user).format_name()
|
||||
|
||||
@staticmethod
|
||||
def get_groups(user):
|
||||
return [g.name for g in user.groups.all()] + [user.profile.state.name]
|
||||
|
||||
@@ -145,6 +145,7 @@ class DiscordHooksTestCase(TestCase):
|
||||
class DiscordViewsTestCase(WebTest):
|
||||
def setUp(self):
|
||||
self.member = AuthUtils.create_member('auth_member')
|
||||
AuthUtils.add_main_character(self.member, 'test character', '1234', '2345', 'test corp', 'testc')
|
||||
add_permissions()
|
||||
|
||||
def login(self):
|
||||
@@ -198,11 +199,11 @@ class DiscordManagerTestCase(TestCase):
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def test__sanitize_groupname(self):
|
||||
test_group_name = ' Group Name_Test_'
|
||||
group_name = DiscordOAuthManager._sanitize_groupname(test_group_name)
|
||||
def test__sanitize_group_name(self):
|
||||
test_group_name = str(10**103)
|
||||
group_name = DiscordOAuthManager._sanitize_group_name(test_group_name)
|
||||
|
||||
self.assertEqual(group_name, 'GroupName_Test')
|
||||
self.assertEqual(group_name, test_group_name[:100])
|
||||
|
||||
def test_generate_Bot_add_url(self):
|
||||
bot_add_url = DiscordOAuthManager.generate_bot_add_url()
|
||||
@@ -245,18 +246,20 @@ class DiscordManagerTestCase(TestCase):
|
||||
|
||||
headers = {'accept': 'application/json', 'authorization': 'Bearer accesstoken'}
|
||||
|
||||
m.register_uri('POST',
|
||||
manager.DISCORD_URL + '/invites/' + str(settings.DISCORD_INVITE_CODE),
|
||||
request_headers=headers,
|
||||
text='{}')
|
||||
|
||||
m.register_uri('GET',
|
||||
manager.DISCORD_URL + "/users/@me",
|
||||
request_headers=headers,
|
||||
text=json.dumps({'id': "123456"}))
|
||||
|
||||
headers = {'accept': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
||||
|
||||
m.register_uri('PUT',
|
||||
manager.DISCORD_URL + '/guilds/' + str(settings.DISCORD_GUILD_ID) + '/members/123456',
|
||||
request_headers=headers,
|
||||
text='{}')
|
||||
|
||||
# Act
|
||||
return_value = DiscordOAuthManager.add_user('abcdef')
|
||||
return_value = DiscordOAuthManager.add_user('abcdef', [])
|
||||
|
||||
# Assert
|
||||
self.assertEqual(return_value, '123456')
|
||||
@@ -328,7 +331,7 @@ class DiscordManagerTestCase(TestCase):
|
||||
@requests_mock.Mocker()
|
||||
def test_update_groups(self, group_cache, m):
|
||||
# Arrange
|
||||
groups = ['Member', 'Blue', 'Special Group']
|
||||
groups = ['Member', 'Blue', 'SpecialGroup']
|
||||
|
||||
group_cache.return_value = [{'id': 111, 'name': 'Member'},
|
||||
{'id': 222, 'name': 'Blue'},
|
||||
|
||||
@@ -345,7 +345,7 @@ class DiscourseManager:
|
||||
|
||||
@staticmethod
|
||||
def update_groups(user):
|
||||
groups = []
|
||||
groups = [DiscourseManager._sanitize_groupname(user.profile.state.name)]
|
||||
for g in user.groups.all():
|
||||
groups.append(DiscourseManager._sanitize_groupname(str(g)))
|
||||
logger.debug("Updating discourse user %s groups to %s" % (user, groups))
|
||||
|
||||
@@ -4,16 +4,20 @@ import string
|
||||
import re
|
||||
from django.db import connections
|
||||
from passlib.hash import bcrypt
|
||||
from django.conf import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
TABLE_PREFIX = getattr(settings, 'IPS4_TABLE_PREFIX', '')
|
||||
|
||||
|
||||
class Ips4Manager:
|
||||
SQL_ADD_USER = r"INSERT INTO core_members (name, email, members_pass_hash, members_pass_salt, " \
|
||||
r"member_group_id) VALUES (%s, %s, %s, %s, %s)"
|
||||
SQL_GET_ID = r"SELECT member_id FROM core_members WHERE name = %s"
|
||||
SQL_UPDATE_PASSWORD = r"UPDATE core_members SET members_pass_hash = %s, members_pass_salt = %s WHERE name = %s"
|
||||
SQL_DEL_USER = r"DELETE FROM core_members WHERE member_id = %s"
|
||||
SQL_ADD_USER = r"INSERT INTO %score_members (name, email, members_pass_hash, members_pass_salt, " \
|
||||
r"member_group_id) VALUES (%%s, %%s, %%s, %%s, %%s)" % TABLE_PREFIX
|
||||
SQL_GET_ID = r"SELECT member_id FROM %score_members WHERE name = %%s" % TABLE_PREFIX
|
||||
SQL_UPDATE_PASSWORD = r"UPDATE %score_members SET members_pass_hash = %%s, members_pass_salt = %%s WHERE name = %%s" % TABLE_PREFIX
|
||||
SQL_DEL_USER = r"DELETE FROM %score_members WHERE member_id = %%s" % TABLE_PREFIX
|
||||
|
||||
MEMBER_GROUP_ID = 3
|
||||
|
||||
|
||||
@@ -5,26 +5,30 @@ import re
|
||||
|
||||
from django.db import connections
|
||||
from passlib.hash import bcrypt
|
||||
from django.conf import settings
|
||||
|
||||
# requires yum install libffi-devel and pip install bcrypt
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
TABLE_PREFIX = getattr(settings, 'MARKET_TABLE_PREFIX', 'fos_')
|
||||
|
||||
|
||||
class MarketManager:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
SQL_ADD_USER = r"INSERT INTO fos_user (username, username_canonical, email, email_canonical, enabled, salt," \
|
||||
SQL_ADD_USER = r"INSERT INTO %suser (username, username_canonical, email, email_canonical, enabled, salt," \
|
||||
r"password, locked, expired, roles, credentials_expired, characterid, characterName)" \
|
||||
r"VALUES (%s, %s, %s, %s, 1,%s, %s, 0, 0, 'a:0:{}', 0, %s, %s) "
|
||||
SQL_GET_USER_ID = r"SELECT id FROM fos_user WHERE username = %s"
|
||||
SQL_DISABLE_USER = r"UPDATE fos_user SET enabled = '0' WHERE username = %s"
|
||||
SQL_ENABLE_USER = r"UPDATE fos_user SET enabled = '1' WHERE username = %s"
|
||||
SQL_UPDATE_PASSWORD = r"UPDATE fos_user SET password = %s, salt = %s WHERE username = %s"
|
||||
SQL_CHECK_EMAIL = r"SELECT email FROM fos_user WHERE email = %s"
|
||||
SQL_CHECK_USERNAME = r"SELECT username FROM fos_user WHERE username = %s"
|
||||
SQL_UPDATE_USER = r"UPDATE fos_user SET password = %s, salt = %s, enabled = '1' WHERE username = %s"
|
||||
r"VALUES (%%s, %%s, %%s, %%s, 1,%%s, %%s, 0, 0, 'a:0:{}', 0, %%s, %%s) " % TABLE_PREFIX
|
||||
SQL_GET_USER_ID = r"SELECT id FROM %suser WHERE username = %%s" % TABLE_PREFIX
|
||||
SQL_DISABLE_USER = r"UPDATE %suser SET enabled = '0' WHERE username = %%s" % TABLE_PREFIX
|
||||
SQL_ENABLE_USER = r"UPDATE %suser SET enabled = '1' WHERE username = %%s" % TABLE_PREFIX
|
||||
SQL_UPDATE_PASSWORD = r"UPDATE %suser SET password = %%s, salt = %%s WHERE username = %%s" % TABLE_PREFIX
|
||||
SQL_CHECK_EMAIL = r"SELECT email FROM %suser WHERE email = %%s" % TABLE_PREFIX
|
||||
SQL_CHECK_USERNAME = r"SELECT username FROM %suser WHERE username = %%s" % TABLE_PREFIX
|
||||
SQL_UPDATE_USER = r"UPDATE %suser SET password = %%s, salt = %%s, enabled = '1' WHERE username = %%s" % TABLE_PREFIX
|
||||
|
||||
@staticmethod
|
||||
def __santatize_username(username):
|
||||
@@ -47,31 +51,31 @@ class MarketManager:
|
||||
|
||||
@classmethod
|
||||
def check_username(cls, username):
|
||||
logger.debug("Checking alliance market username %s" % username)
|
||||
logger.debug("Checking alliance market username %%s" % username)
|
||||
cursor = connections['market'].cursor()
|
||||
cursor.execute(cls.SQL_CHECK_USERNAME, [cls.__santatize_username(username)])
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
logger.debug("Found user %s on alliance market" % username)
|
||||
logger.debug("Found user %%s on alliance market" % username)
|
||||
return True
|
||||
logger.debug("User %s not found on alliance market" % username)
|
||||
logger.debug("User %%s not found on alliance market" % username)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def check_user_email(cls, username, email):
|
||||
logger.debug("Checking if alliance market email exists for user %s" % username)
|
||||
logger.debug("Checking if alliance market email exists for user %%s" % username)
|
||||
cursor = connections['market'].cursor()
|
||||
cursor.execute(cls.SQL_CHECK_EMAIL, [email])
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
logger.debug("Found user %s email address on alliance market" % username)
|
||||
logger.debug("Found user %%s email address on alliance market" % username)
|
||||
return True
|
||||
logger.debug("User %s email address not found on alliance market" % username)
|
||||
logger.debug("User %%s email address not found on alliance market" % username)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def add_user(cls, username, email, characterid, charactername):
|
||||
logger.debug("Adding new market user %s" % username)
|
||||
logger.debug("Adding new market user %%s" % username)
|
||||
plain_password = cls.__generate_random_pass()
|
||||
hash = cls._gen_pwhash(plain_password)
|
||||
salt = cls._get_salt(hash)
|
||||
@@ -79,33 +83,33 @@ class MarketManager:
|
||||
if not cls.check_username(username):
|
||||
if not cls.check_user_email(username, email):
|
||||
try:
|
||||
logger.debug("Adding user %s to alliance market" % username)
|
||||
logger.debug("Adding user %%s to alliance market" % username)
|
||||
cursor = connections['market'].cursor()
|
||||
cursor.execute(cls.SQL_ADD_USER, [username_clean, username_clean, email, email, salt,
|
||||
hash, characterid, charactername])
|
||||
return username_clean, plain_password
|
||||
except:
|
||||
logger.debug("Unsuccessful attempt to add market user %s" % username)
|
||||
logger.debug("Unsuccessful attempt to add market user %%s" % username)
|
||||
return "", ""
|
||||
else:
|
||||
logger.debug("Alliance market email %s already exists Updating instead" % email)
|
||||
logger.debug("Alliance market email %%s already exists Updating instead" % email)
|
||||
username_clean, password = cls.update_user_info(username)
|
||||
return username_clean, password
|
||||
else:
|
||||
logger.debug("Alliance market username %s already exists Updating instead" % username)
|
||||
logger.debug("Alliance market username %%s already exists Updating instead" % username)
|
||||
username_clean, password = cls.update_user_info(username)
|
||||
return username_clean, password
|
||||
|
||||
@classmethod
|
||||
def disable_user(cls, username):
|
||||
logger.debug("Disabling alliance market user %s " % username)
|
||||
logger.debug("Disabling alliance market user %%s " % username)
|
||||
cursor = connections['market'].cursor()
|
||||
cursor.execute(cls.SQL_DISABLE_USER, [username])
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def update_custom_password(cls, username, plain_password):
|
||||
logger.debug("Updating alliance market user %s password" % username)
|
||||
logger.debug("Updating alliance market user %%s password" % username)
|
||||
if cls.check_username(username):
|
||||
username_clean = cls.__santatize_username(username)
|
||||
hash = cls._gen_pwhash(plain_password)
|
||||
@@ -114,12 +118,12 @@ class MarketManager:
|
||||
cursor.execute(cls.SQL_UPDATE_PASSWORD, [hash, salt, username_clean])
|
||||
return plain_password
|
||||
else:
|
||||
logger.error("Unable to update alliance market user %s password" % username)
|
||||
logger.error("Unable to update alliance market user %%s password" % username)
|
||||
return ""
|
||||
|
||||
@classmethod
|
||||
def update_user_password(cls, username):
|
||||
logger.debug("Updating alliance market user %s password" % username)
|
||||
logger.debug("Updating alliance market user %%s password" % username)
|
||||
if cls.check_username(username):
|
||||
username_clean = cls.__santatize_username(username)
|
||||
plain_password = cls.__generate_random_pass()
|
||||
@@ -129,12 +133,12 @@ class MarketManager:
|
||||
cursor.execute(cls.SQL_UPDATE_PASSWORD, [hash, salt, username_clean])
|
||||
return plain_password
|
||||
else:
|
||||
logger.error("Unable to update alliance market user %s password" % username)
|
||||
logger.error("Unable to update alliance market user %%s password" % username)
|
||||
return ""
|
||||
|
||||
@classmethod
|
||||
def update_user_info(cls, username):
|
||||
logger.debug("Updating alliance market user %s" % username)
|
||||
logger.debug("Updating alliance market user %%s" % username)
|
||||
try:
|
||||
username_clean = cls.__santatize_username(username)
|
||||
plain_password = cls.__generate_random_pass()
|
||||
@@ -144,5 +148,5 @@ class MarketManager:
|
||||
cursor.execute(cls.SQL_UPDATE_USER, [hash, salt, username_clean])
|
||||
return username_clean, plain_password
|
||||
except:
|
||||
logger.debug("Alliance market update user failed for %s" % username)
|
||||
logger.debug("Alliance market update user failed for %%s" % username)
|
||||
return "", ""
|
||||
|
||||
@@ -82,11 +82,9 @@ class MumbleUser(AbstractServiceModel):
|
||||
def update_groups(self, groups: Group=None):
|
||||
if groups is None:
|
||||
groups = self.user.groups.all()
|
||||
groups_str = []
|
||||
groups_str = [self.user.profile.state.name]
|
||||
for group in groups:
|
||||
groups_str.append(str(group.name))
|
||||
if len(groups) == 0:
|
||||
groups_str.append('empty')
|
||||
safe_groups = ','.join(set([g.replace(' ', '-') for g in groups_str]))
|
||||
logger.info("Updating mumble user {} groups to {}".format(self.user, safe_groups))
|
||||
self.groups = safe_groups
|
||||
|
||||
@@ -136,7 +136,9 @@ class MumbleViewsTestCase(TestCase):
|
||||
mumble_user = MumbleUser.objects.get(user=self.member)
|
||||
self.assertEqual(mumble_user.username, expected_username)
|
||||
self.assertTrue(mumble_user.pwhash)
|
||||
self.assertEqual('Member', mumble_user.groups)
|
||||
self.assertIn('Guest', mumble_user.groups)
|
||||
self.assertIn('Member', mumble_user.groups)
|
||||
self.assertIn(',', mumble_user.groups)
|
||||
|
||||
def test_deactivate_post(self):
|
||||
self.login()
|
||||
|
||||
@@ -45,11 +45,9 @@ class OpenfireTasks:
|
||||
user = User.objects.get(pk=pk)
|
||||
logger.debug("Updating jabber groups for user %s" % user)
|
||||
if OpenfireTasks.has_account(user):
|
||||
groups = []
|
||||
groups = [user.profile.state.name]
|
||||
for group in user.groups.all():
|
||||
groups.append(str(group.name))
|
||||
if len(groups) == 0:
|
||||
groups.append('empty')
|
||||
logger.debug("Updating user %s jabber groups to %s" % (user, groups))
|
||||
try:
|
||||
OpenfireManager.update_user_groups(user.openfire.username, groups)
|
||||
|
||||
@@ -14,40 +14,43 @@ from django.conf import settings
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
TABLE_PREFIX = getattr(settings, 'PHPBB3_TABLE_PREFIX', 'phpbb_')
|
||||
|
||||
|
||||
class Phpbb3Manager:
|
||||
SQL_ADD_USER = r"INSERT INTO phpbb_users (username, username_clean, " \
|
||||
SQL_ADD_USER = r"INSERT INTO %susers (username, username_clean, " \
|
||||
r"user_password, user_email, group_id, user_regdate, user_permissions, " \
|
||||
r"user_sig, user_lang) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, 'en')"
|
||||
r"user_sig, user_lang) VALUES (%%s, %%s, %%s, %%s, %%s, %%s, %%s, %%s, 'en')" % TABLE_PREFIX
|
||||
|
||||
SQL_DEL_USER = r"DELETE FROM phpbb_users where username = %s"
|
||||
SQL_DEL_USER = r"DELETE FROM %susers where username = %%s" % TABLE_PREFIX
|
||||
|
||||
SQL_DIS_USER = r"UPDATE phpbb_users SET user_email= %s, user_password=%s WHERE username = %s"
|
||||
SQL_DIS_USER = r"UPDATE %susers SET user_email= %%s, user_password=%%s WHERE username = %%s" % TABLE_PREFIX
|
||||
|
||||
SQL_USER_ID_FROM_USERNAME = r"SELECT user_id from phpbb_users WHERE username = %s"
|
||||
SQL_USER_ID_FROM_USERNAME = r"SELECT user_id from %susers WHERE username = %%s" % TABLE_PREFIX
|
||||
|
||||
SQL_ADD_USER_GROUP = r"INSERT INTO phpbb_user_group (group_id, user_id, user_pending) VALUES (%s, %s, %s)"
|
||||
SQL_ADD_USER_GROUP = r"INSERT INTO %suser_group (group_id, user_id, user_pending) VALUES (%%s, %%s, %%s)" % TABLE_PREFIX
|
||||
|
||||
SQL_GET_GROUP_ID = r"SELECT group_id from phpbb_groups WHERE group_name = %s"
|
||||
SQL_GET_GROUP_ID = r"SELECT group_id from %sgroups WHERE group_name = %%s" % TABLE_PREFIX
|
||||
|
||||
SQL_ADD_GROUP = r"INSERT INTO phpbb_groups (group_name,group_desc,group_legend) VALUES (%s,%s,0)"
|
||||
SQL_ADD_GROUP = r"INSERT INTO %sgroups (group_name,group_desc,group_legend) VALUES (%%s,%%s,0)" % TABLE_PREFIX
|
||||
|
||||
SQL_UPDATE_USER_PASSWORD = r"UPDATE phpbb_users SET user_password = %s WHERE username = %s"
|
||||
SQL_UPDATE_USER_PASSWORD = r"UPDATE %susers SET user_password = %%s WHERE username = %%s" % TABLE_PREFIX
|
||||
|
||||
SQL_REMOVE_USER_GROUP = r"DELETE FROM phpbb_user_group WHERE user_id=%s AND group_id=%s "
|
||||
SQL_REMOVE_USER_GROUP = r"DELETE FROM %suser_group WHERE user_id=%%s AND group_id=%%s " % TABLE_PREFIX
|
||||
|
||||
SQL_GET_ALL_GROUPS = r"SELECT group_id, group_name FROM phpbb_groups"
|
||||
SQL_GET_ALL_GROUPS = r"SELECT group_id, group_name FROM %sgroups" % TABLE_PREFIX
|
||||
|
||||
SQL_GET_USER_GROUPS = r"SELECT phpbb_groups.group_name FROM phpbb_groups , phpbb_user_group WHERE " \
|
||||
r"phpbb_user_group.group_id = phpbb_groups.group_id AND user_id=%s"
|
||||
SQL_GET_USER_GROUPS = r"SELECT %(prefix)sgroups.group_name FROM %(prefix)sgroups , %(prefix)suser_group WHERE " \
|
||||
r"%(prefix)suser_group.group_id = %(prefix)sgroups.group_id AND user_id=%%s" % {'prefix': TABLE_PREFIX}
|
||||
|
||||
SQL_ADD_USER_AVATAR = r"UPDATE phpbb_users SET user_avatar_type=2, user_avatar_width=64, user_avatar_height=64, " \
|
||||
"user_avatar=%s WHERE user_id = %s"
|
||||
SQL_ADD_USER_AVATAR = r"UPDATE %susers SET user_avatar_type=2, user_avatar_width=64, user_avatar_height=64, " \
|
||||
"user_avatar=%%s WHERE user_id = %%s" % TABLE_PREFIX
|
||||
|
||||
SQL_CLEAR_USER_PERMISSIONS = r"UPDATE phpbb_users SET user_permissions = '' WHERE user_Id = %s"
|
||||
SQL_CLEAR_USER_PERMISSIONS = r"UPDATE %susers SET user_permissions = '' WHERE user_id = %%s" % TABLE_PREFIX
|
||||
|
||||
SQL_DEL_SESSION = r"DELETE FROM phpbb_sessions where session_user_id = %s"
|
||||
SQL_DEL_SESSION = r"DELETE FROM %ssessions where session_user_id = %%s" % TABLE_PREFIX
|
||||
|
||||
SQL_DEL_AUTOLOGIN = r"DELETE FROM phpbb_sessions_keys where user_id = %s"
|
||||
SQL_DEL_AUTOLOGIN = r"DELETE FROM %ssessions_keys where user_id = %%s" % TABLE_PREFIX
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@@ -40,11 +40,9 @@ class Phpbb3Tasks:
|
||||
user = User.objects.get(pk=pk)
|
||||
logger.debug("Updating phpbb3 groups for user %s" % user)
|
||||
if Phpbb3Tasks.has_account(user):
|
||||
groups = []
|
||||
groups = [user.profile.state.name]
|
||||
for group in user.groups.all():
|
||||
groups.append(str(group.name))
|
||||
if len(groups) == 0:
|
||||
groups.append('empty')
|
||||
logger.debug("Updating user %s phpbb3 groups to %s" % (user, groups))
|
||||
try:
|
||||
Phpbb3Manager.update_groups(user.phpbb3.username, groups)
|
||||
|
||||
@@ -38,13 +38,10 @@ class SeatTasks:
|
||||
def update_roles(self, pk):
|
||||
user = User.objects.get(pk=pk)
|
||||
logger.debug("Updating SeAT roles for user %s" % user)
|
||||
groups = []
|
||||
if SeatTasks.has_account(user):
|
||||
groups = [user.profile.state.name]
|
||||
for group in user.groups.all():
|
||||
groups.append(str(group.name))
|
||||
if len(groups) == 0:
|
||||
logger.debug("No syncgroups found for user. Adding empty group.")
|
||||
groups.append('empty')
|
||||
logger.debug("Updating user %s SeAT roles to %s" % (user, groups))
|
||||
try:
|
||||
SeatManager.update_roles(user.seat.username, groups)
|
||||
|
||||
@@ -152,7 +152,6 @@ class SeatViewsTestCase(TestCase):
|
||||
self.assertContains(response, expected_username)
|
||||
seat_user = SeatUser.objects.get(user=self.member)
|
||||
self.assertEqual(seat_user.username, expected_username)
|
||||
self.assertTrue(manager.synchronize_eveapis.called)
|
||||
|
||||
@mock.patch(MODULE_PATH + '.tasks.SeatManager')
|
||||
def test_deactivate(self, manager):
|
||||
|
||||
@@ -39,7 +39,6 @@ def activate_seat(request):
|
||||
logger.info("Successfully activated SeAT for user %s" % request.user)
|
||||
messages.add_message(request, messages.SUCCESS, _('Successfully activated your %(service)s account.') %
|
||||
SERVICE_NAME)
|
||||
SeatManager.synchronize_eveapis(request.user)
|
||||
credentials = {
|
||||
'username': request.user.seat.username,
|
||||
'password': result[1],
|
||||
|
||||
@@ -12,35 +12,38 @@ from django.conf import settings
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
TABLE_PREFIX = getattr(settings, 'SMF_TABLE_PREFIX', 'smf_')
|
||||
|
||||
|
||||
class SmfManager:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
SQL_ADD_USER = r"INSERT INTO smf_members (member_name, passwd, email_address, date_registered, real_name," \
|
||||
SQL_ADD_USER = r"INSERT INTO %smembers (member_name, passwd, email_address, date_registered, real_name," \
|
||||
r" buddy_list, message_labels, openid_uri, signature, ignore_boards) " \
|
||||
r"VALUES (%s, %s, %s, %s, %s, 0, 0, 0, 0, 0)"
|
||||
r"VALUES (%%s, %%s, %%s, %%s, %%s, 0, 0, 0, 0, 0)" % TABLE_PREFIX
|
||||
|
||||
SQL_DEL_USER = r"DELETE FROM smf_members where member_name = %s"
|
||||
SQL_DEL_USER = r"DELETE FROM %smembers where member_name = %%s" % TABLE_PREFIX
|
||||
|
||||
SQL_DIS_USER = r"UPDATE smf_members SET email_address = %s, passwd = %s WHERE member_name = %s"
|
||||
SQL_DIS_USER = r"UPDATE %smembers SET email_address = %%s, passwd = %%s WHERE member_name = %%s" % TABLE_PREFIX
|
||||
|
||||
SQL_USER_ID_FROM_USERNAME = r"SELECT id_member from smf_members WHERE member_name = %s"
|
||||
SQL_USER_ID_FROM_USERNAME = r"SELECT id_member from %smembers WHERE member_name = %%s" % TABLE_PREFIX
|
||||
|
||||
SQL_ADD_USER_GROUP = r"UPDATE smf_members SET additional_groups = %s WHERE id_member = %s"
|
||||
SQL_ADD_USER_GROUP = r"UPDATE %smembers SET additional_groups = %%s WHERE id_member = %%s" % TABLE_PREFIX
|
||||
|
||||
SQL_GET_GROUP_ID = r"SELECT id_group from smf_membergroups WHERE group_name = %s"
|
||||
SQL_GET_GROUP_ID = r"SELECT id_group from %smembergroups WHERE group_name = %%s" % TABLE_PREFIX
|
||||
|
||||
SQL_ADD_GROUP = r"INSERT INTO smf_membergroups (group_name,description) VALUES (%s,%s)"
|
||||
SQL_ADD_GROUP = r"INSERT INTO %smembergroups (group_name,description) VALUES (%%s,%%s)" % TABLE_PREFIX
|
||||
|
||||
SQL_UPDATE_USER_PASSWORD = r"UPDATE smf_members SET passwd = %s WHERE member_name = %s"
|
||||
SQL_UPDATE_USER_PASSWORD = r"UPDATE %smembers SET passwd = %%s WHERE member_name = %%s" % TABLE_PREFIX
|
||||
|
||||
SQL_REMOVE_USER_GROUP = r"UPDATE smf_members SET additional_groups = %s WHERE id_member = %s"
|
||||
SQL_REMOVE_USER_GROUP = r"UPDATE %smembers SET additional_groups = %%s WHERE id_member = %%s" % TABLE_PREFIX
|
||||
|
||||
SQL_GET_ALL_GROUPS = r"SELECT id_group, group_name FROM smf_membergroups"
|
||||
SQL_GET_ALL_GROUPS = r"SELECT id_group, group_name FROM %smembergroups" % TABLE_PREFIX
|
||||
|
||||
SQL_GET_USER_GROUPS = r"SELECT additional_groups FROM smf_members WHERE id_member = %s"
|
||||
SQL_GET_USER_GROUPS = r"SELECT additional_groups FROM %smembers WHERE id_member = %%s" % TABLE_PREFIX
|
||||
|
||||
SQL_ADD_USER_AVATAR = r"UPDATE smf_members SET avatar = %s WHERE id_member = %s"
|
||||
SQL_ADD_USER_AVATAR = r"UPDATE %smembers SET avatar = %%s WHERE id_member = %%s" % TABLE_PREFIX
|
||||
|
||||
@staticmethod
|
||||
def _sanitize_groupname(name):
|
||||
|
||||
@@ -44,11 +44,9 @@ class SmfTasks:
|
||||
user = User.objects.get(pk=pk)
|
||||
logger.debug("Updating smf groups for user %s" % user)
|
||||
if SmfTasks.has_account(user):
|
||||
groups = []
|
||||
groups = [user.profile.state.name]
|
||||
for group in user.groups.all():
|
||||
groups.append(str(group.name))
|
||||
if len(groups) == 0:
|
||||
groups.append('empty')
|
||||
logger.debug("Updating user %s smf groups to %s" % (user, groups))
|
||||
try:
|
||||
SmfManager.update_groups(user.smf.username, groups)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from django.contrib import admin
|
||||
from .models import AuthTS, Teamspeak3User
|
||||
from .models import AuthTS, Teamspeak3User, StateGroup
|
||||
|
||||
|
||||
class Teamspeak3UserAdmin(admin.ModelAdmin):
|
||||
@@ -12,5 +12,11 @@ class AuthTSgroupAdmin(admin.ModelAdmin):
|
||||
filter_horizontal = ('ts_group',)
|
||||
|
||||
|
||||
@admin.register(StateGroup)
|
||||
class StateGroupAdmin(admin.ModelAdmin):
|
||||
list_display = ('state', 'ts_group')
|
||||
search_fields = ('state__name', 'ts_group__ts_group_name')
|
||||
|
||||
|
||||
admin.site.register(AuthTS, AuthTSgroupAdmin)
|
||||
admin.site.register(Teamspeak3User, Teamspeak3UserAdmin)
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.10 on 2018-02-23 06:13
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('authentication', '0015_user_profiles'),
|
||||
('teamspeak3', '0004_service_permissions'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='StateGroup',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('state', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='authentication.State')),
|
||||
('ts_group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='teamspeak3.TSgroup')),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -1,5 +1,6 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User, Group
|
||||
from allianceauth.authentication.models import State
|
||||
|
||||
|
||||
class Teamspeak3User(models.Model):
|
||||
@@ -50,3 +51,8 @@ class UserTSgroup(models.Model):
|
||||
|
||||
def __str__(self):
|
||||
return self.user.name
|
||||
|
||||
|
||||
class StateGroup(models.Model):
|
||||
state = models.ForeignKey(State, on_delete=models.CASCADE)
|
||||
ts_group = models.ForeignKey(TSgroup, on_delete=models.CASCADE)
|
||||
|
||||
@@ -5,9 +5,9 @@ from django.db.models.signals import m2m_changed
|
||||
from django.db.models.signals import post_delete
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
|
||||
from allianceauth.authentication.signals import state_changed
|
||||
from .tasks import Teamspeak3Tasks
|
||||
from .models import AuthTS
|
||||
from .models import AuthTS, StateGroup
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -34,3 +34,16 @@ def post_save_authts(sender, instance, *args, **kwargs):
|
||||
def post_delete_authts(sender, instance, *args, **kwargs):
|
||||
logger.debug("Received post_delete signal from %s" % instance)
|
||||
transaction.on_commit(trigger_all_ts_update)
|
||||
|
||||
|
||||
# it's literally the same logic so just recycle the receiver
|
||||
post_save.connect(post_save_authts, sender=StateGroup)
|
||||
post_delete.connect(post_delete_authts, sender=StateGroup)
|
||||
|
||||
|
||||
@receiver(state_changed)
|
||||
def check_groups_on_state_change(sender, user, state, **kwargs):
|
||||
def trigger_update():
|
||||
Teamspeak3Tasks.update_groups.delay(user.pk)
|
||||
logger.debug("Received state_changed signal from {}".format(user))
|
||||
transaction.on_commit(trigger_update)
|
||||
|
||||
@@ -69,6 +69,8 @@ class Teamspeak3Tasks:
|
||||
for filtered_group in filtered_groups:
|
||||
for ts_group in filtered_group.ts_group.all():
|
||||
groups[ts_group.ts_group_name] = ts_group.ts_group_id
|
||||
for stategroup in user.profile.state.stategroup_set.all():
|
||||
groups[stategroup.ts_group.ts_group_name] = stategroup.ts_group.ts_group_id
|
||||
logger.debug("Updating user %s teamspeak3 groups to %s" % (user, groups))
|
||||
try:
|
||||
with Teamspeak3Manager() as ts3man:
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<h1 class="page-header text-center">{% trans "Verify Teamspeak Identity" %}</h1>
|
||||
<div class="container-fluid">
|
||||
<div class="col-md-4 col-md-offset-4">
|
||||
<a href="ts3server://{{ TEAMSPEAK3_PUBLIC_URL }}?token={{ authinfo.teamspeak3_perm_key }}&nickname={{ authinfo.teamspeak3_uid }}" class="btn btn-primary btn-block btn-lg" title="Join">{% trans "Join Server" %}</a>
|
||||
<a href="ts3server://{{ public_url }}?token={{ authinfo.teamspeak3_perm_key }}&nickname={{ authinfo.teamspeak3_uid }}" class="btn btn-primary btn-block btn-lg" title="Join">{% trans "Join Server" %}</a>
|
||||
<br/>
|
||||
<form class="form-signin" role="form" action="{% url 'teamspeak3:verify' %}" method="POST">
|
||||
{% csrf_token %}
|
||||
|
||||
@@ -7,9 +7,8 @@ from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db.models import signals
|
||||
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
|
||||
from .auth_hooks import Teamspeak3Service
|
||||
from .models import Teamspeak3User, AuthTS, TSgroup
|
||||
from .models import Teamspeak3User, AuthTS, TSgroup, StateGroup
|
||||
from .tasks import Teamspeak3Tasks
|
||||
from .signals import m2m_changed_authts_group, post_save_authts, post_delete_authts
|
||||
|
||||
@@ -31,13 +30,14 @@ class Teamspeak3HooksTestCase(TestCase):
|
||||
member = AuthUtils.create_member(self.member)
|
||||
Teamspeak3User.objects.create(user=member, uid=self.member, perm_key='123ABC')
|
||||
self.none_user = 'none_user'
|
||||
none_user = AuthUtils.create_user(self.none_user)
|
||||
|
||||
AuthUtils.create_user(self.none_user)
|
||||
state = member.profile.state
|
||||
ts_member_group = TSgroup.objects.create(ts_group_id=1, ts_group_name='Member')
|
||||
ts_blue_group = TSgroup.objects.create(ts_group_id=2, ts_group_name='Blue')
|
||||
ts_state_group = TSgroup.objects.create(ts_group_id=2, ts_group_name='State')
|
||||
m2m_member_group = AuthTS.objects.create(auth_group=member.groups.all()[0])
|
||||
m2m_member_group.ts_group.add(ts_member_group)
|
||||
m2m_member_group.save()
|
||||
StateGroup.objects.create(state=state, ts_group=ts_state_group)
|
||||
self.service = Teamspeak3Service
|
||||
add_permissions()
|
||||
|
||||
@@ -60,7 +60,7 @@ class Teamspeak3HooksTestCase(TestCase):
|
||||
instance = manager.return_value.__enter__.return_value
|
||||
service = self.service()
|
||||
service.update_all_groups()
|
||||
# Check member and blue user have groups updated
|
||||
# Check user has groups updated
|
||||
self.assertTrue(instance.update_groups.called)
|
||||
self.assertEqual(instance.update_groups.call_count, 1)
|
||||
|
||||
@@ -74,7 +74,7 @@ class Teamspeak3HooksTestCase(TestCase):
|
||||
self.assertTrue(instance.update_groups.called)
|
||||
args, kwargs = instance.update_groups.call_args
|
||||
# update_groups(user.teamspeak3.uid, groups)
|
||||
self.assertEqual({'Member': 1}, args[1]) # Check groups
|
||||
self.assertEqual({'Member': 1, 'State': 2}, args[1]) # Check groups
|
||||
self.assertEqual(self.member, args[0]) # Check uid
|
||||
|
||||
# Check none user does not have groups updated
|
||||
@@ -278,3 +278,15 @@ class Teamspeak3SignalsTestCase(TestCase):
|
||||
self.m2m_member.delete() # Trigger delete signal
|
||||
|
||||
self.assertTrue(trigger_all_ts_update.called)
|
||||
|
||||
@mock.patch(MODULE_PATH + '.signals.transaction')
|
||||
@mock.patch(MODULE_PATH + '.signals.Teamspeak3Tasks.update_groups.delay')
|
||||
def test_state_changed(self, update_groups, transaction):
|
||||
# Overload transaction.on_commit so everything happens synchronously
|
||||
transaction.on_commit = lambda fn: fn()
|
||||
|
||||
state = AuthUtils.create_state('test', 1000, disconnect_signals=True)
|
||||
self.member.profile.state = state
|
||||
self.member.profile.save()
|
||||
|
||||
self.assertTrue(update_groups.called)
|
||||
|
||||
@@ -3,7 +3,7 @@ import logging
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.shortcuts import render, redirect
|
||||
|
||||
from django.conf import settings
|
||||
from .manager import Teamspeak3Manager
|
||||
from .forms import TeamspeakJoinForm
|
||||
from .models import Teamspeak3User
|
||||
@@ -20,7 +20,6 @@ def activate_teamspeak3(request):
|
||||
logger.debug("activate_teamspeak3 called by user %s" % request.user)
|
||||
|
||||
character = request.user.profile.main_character
|
||||
ticker = character.corporation_ticker
|
||||
with Teamspeak3Manager() as ts3man:
|
||||
logger.debug("Adding TS3 user for user %s with main character %s" % (request.user, character))
|
||||
result = ts3man.add_user(Teamspeak3Tasks.get_username(request.user))
|
||||
@@ -56,6 +55,7 @@ def verify_teamspeak3(request):
|
||||
'form': form,
|
||||
'authinfo': {'teamspeak3_uid': request.user.teamspeak3.uid,
|
||||
'teamspeak3_perm_key': request.user.teamspeak3.perm_key},
|
||||
'public_url': settings.TEAMSPEAK3_PUBLIC_URL,
|
||||
}
|
||||
return render(request, 'services/teamspeak3/teamspeakjoin.html', context=context)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import logging
|
||||
|
||||
import redis
|
||||
from celery import shared_task
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from .hooks import ServicesHook
|
||||
|
||||
REDIS_CLIENT = redis.Redis()
|
||||
@@ -38,7 +38,8 @@ def only_one(function=None, key="", timeout=None):
|
||||
|
||||
|
||||
@shared_task(bind=True)
|
||||
def validate_services(self, user):
|
||||
def validate_services(self, pk):
|
||||
user = User.objects.get(pk=pk)
|
||||
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():
|
||||
|
||||
@@ -18,7 +18,7 @@ class ServicesTasksTestCase(TestCase):
|
||||
|
||||
services_hook.get_services.return_value = [svc]
|
||||
|
||||
validate_services.delay(user=self.member)
|
||||
validate_services.delay(self.member.pk)
|
||||
|
||||
self.assertTrue(services_hook.get_services.called)
|
||||
self.assertTrue(svc.validate_user.called)
|
||||
|
||||
@@ -42,7 +42,6 @@
|
||||
</ul>
|
||||
<form id="f-lang-select" class="navbar-form navbar-right" action="{% url 'set_language' %}" method="post">
|
||||
{% csrf_token %}
|
||||
<input name="next" type="hidden" value="{{ request.get_full_path|slice:'3:' }}"/>
|
||||
<div class="form-group">
|
||||
<select onchange="this.form.submit()" class="form-control" id="lang-select" name="language">
|
||||
{% get_language_info_list for LANGUAGES as languages %}
|
||||
|
||||
@@ -32,14 +32,26 @@ class TimerForm(forms.ModelForm):
|
||||
kwargs.update({'initial': initial})
|
||||
super(TimerForm, self).__init__(*args, **kwargs)
|
||||
|
||||
structure_choices = [('POCO', 'POCO'), ('I-HUB', 'I-HUB'), ('POS[S]', 'POS[S]'),
|
||||
('POS[M]', 'POS[M]'), ('POS[L]', 'POS[L]'), ('Citadel[M]', 'Citadel[M]'),
|
||||
('Citadel[L]', 'Citadel[L]'), ('Citadel[XL]', 'Citadel[XL]'),
|
||||
structure_choices = [('POCO', 'POCO'),
|
||||
('I-HUB', 'I-HUB'),
|
||||
('POS[S]', 'POS[S]'),
|
||||
('POS[M]', 'POS[M]'),
|
||||
('POS[L]', 'POS[L]'),
|
||||
('Citadel[M]', 'Citadel[M]'),
|
||||
('Citadel[L]', 'Citadel[L]'),
|
||||
('Citadel[XL]', 'Citadel[XL]'),
|
||||
('Engineering Complex[M]', 'Engineering Complex[M]'),
|
||||
('Engineering Complex[L]', 'Engineering Complex[L]'),
|
||||
('Engineering Complex[XL]', 'Engineering Complex[XL]'),
|
||||
('Station', 'Station'), ('TCU', 'TCU'), (_('Other'), _('Other'))]
|
||||
objective_choices = [('Friendly', _('Friendly')), ('Hostile', _('Hostile')), ('Neutral', _('Neutral'))]
|
||||
('Refinery[M]', 'Refinery[M]'),
|
||||
('Refinery[L]', 'Refinery[L]'),
|
||||
('Station', 'Station'),
|
||||
('TCU', 'TCU'),
|
||||
('Moon Mining Cycle', 'Moon Mining Cycle'),
|
||||
(_('Other'), _('Other'))]
|
||||
objective_choices = [('Friendly', _('Friendly')),
|
||||
('Hostile', _('Hostile')),
|
||||
('Neutral', _('Neutral'))]
|
||||
|
||||
details = forms.CharField(max_length=254, required=True, label=_('Details'))
|
||||
system = forms.CharField(max_length=254, required=True, label=_("System"))
|
||||
|
||||
@@ -118,7 +118,6 @@
|
||||
Engineering Complex [XL]
|
||||
</div>
|
||||
{% endifequal %}
|
||||
|
||||
{% ifequal timer.structure "Station" %}
|
||||
<div class="label label-danger">
|
||||
Station
|
||||
@@ -129,6 +128,21 @@
|
||||
TCU
|
||||
</div>
|
||||
{% endifequal %}
|
||||
{% ifequal timer.structure "Refinery[M]" %}
|
||||
<div class="label label-warning">
|
||||
Refinery [M]
|
||||
</div>
|
||||
{% endifequal %}
|
||||
{% ifequal timer.structure "Refinery[L]" %}
|
||||
<div class="label label-warning">
|
||||
Refinery [L]
|
||||
</div>
|
||||
{% endifequal %}
|
||||
{% ifequal timer.structure "Moon Mining Cycle" %}
|
||||
<div class="label label-success">
|
||||
Moon Mining Cycle
|
||||
</div>
|
||||
{% endifequal %}
|
||||
{% ifequal timer.structure "Other" %}
|
||||
<div class="label label-default">
|
||||
Other
|
||||
@@ -263,6 +277,21 @@
|
||||
TCU
|
||||
</div>
|
||||
{% endifequal %}
|
||||
{% ifequal timer.structure "Refinery[M]" %}
|
||||
<div class="label label-warning">
|
||||
Refinery [M]
|
||||
</div>
|
||||
{% endifequal %}
|
||||
{% ifequal timer.structure "Refinery[L]" %}
|
||||
<div class="label label-warning">
|
||||
Refinery [L]
|
||||
</div>
|
||||
{% endifequal %}
|
||||
{% ifequal timer.structure "Moon Mining Cycle" %}
|
||||
<div class="label label-success">
|
||||
Moon Mining Cycle
|
||||
</div>
|
||||
{% endifequal %}
|
||||
{% ifequal timer.structure "Other" %}
|
||||
<div class="label label-default">
|
||||
Other
|
||||
@@ -399,6 +428,21 @@
|
||||
TCU
|
||||
</div>
|
||||
{% endifequal %}
|
||||
{% ifequal timer.structure "Refinery[M]" %}
|
||||
<div class="label label-warning">
|
||||
Refinery [M]
|
||||
</div>
|
||||
{% endifequal %}
|
||||
{% ifequal timer.structure "Refinery[L]" %}
|
||||
<div class="label label-warning">
|
||||
Refinery [L]
|
||||
</div>
|
||||
{% endifequal %}
|
||||
{% ifequal timer.structure "Moon Mining Cycle" %}
|
||||
<div class="label label-success">
|
||||
Moon Mining Cycle
|
||||
</div>
|
||||
{% endifequal %}
|
||||
{% ifequal timer.structure "Other" %}
|
||||
<div class="label label-default">
|
||||
Other
|
||||
|
||||
@@ -9,9 +9,9 @@ import allianceauth.authentication.urls
|
||||
import allianceauth.notifications.urls
|
||||
import allianceauth.groupmanagement.urls
|
||||
import allianceauth.services.urls
|
||||
from allianceauth.authentication.decorators import main_character_required, decorate_url_patterns
|
||||
from allianceauth import NAME
|
||||
from allianceauth import views
|
||||
|
||||
from allianceauth.authentication import hmac_urls
|
||||
from allianceauth.hooks import get_hooks
|
||||
|
||||
@@ -42,13 +42,14 @@ urlpatterns = [
|
||||
url(r'', include(allianceauth.groupmanagement.urls)),
|
||||
|
||||
# Services
|
||||
url(r'', include(allianceauth.services.urls)),
|
||||
url(r'', decorate_url_patterns(allianceauth.services.urls.urlpatterns, main_character_required)),
|
||||
|
||||
# Night mode
|
||||
url(r'^night/', views.NightModeRedirectView.as_view(), name='nightmode')
|
||||
]
|
||||
|
||||
|
||||
# Append app urls
|
||||
app_urls = get_hooks('url_hook')
|
||||
for app in app_urls:
|
||||
urlpatterns += [app().include_pattern]
|
||||
urlpatterns += [url(r'', decorate_url_patterns([app().include_pattern], main_character_required))]
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
docs/_static/images/features/group-admin.png
vendored
BIN
docs/_static/images/features/group-admin.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 51 KiB |
@@ -1,10 +1,5 @@
|
||||
# Menu Hooks
|
||||
|
||||
```eval_rst
|
||||
.. note::
|
||||
Currently most menu items are statically defined in the `base.html` template. Ideally this behaviour will change over time with each module of Alliance Auth providing all of its menu items via the hook. New modules should aim to use the hook over statically adding menu items to the base template.
|
||||
```
|
||||
|
||||
The menu hooks allow you to dynamically specify menu items from your plugin app or service. To achieve this you should subclass or instantiate the `services.hooks.MenuItemHook` class and then register the menu item with one of the hooks.
|
||||
|
||||
To register a MenuItemHook class you would do the following:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
```eval_rst
|
||||
.. note::
|
||||
Currently most URL patterns are statically defined in the project's core urls.py file. Ideally this behaviour will change over time with each module of Alliance Auth providing all of its menu items via the hook. New modules should aim to use the hook over statically adding URL patterns to the project's patterns.
|
||||
URLs added through URL Hooks are protected by a decorator which ensures the requesting user is logged in and has a main character set.
|
||||
```
|
||||
|
||||
The URL hooks allow you to dynamically specify URL patterns from your plugin app or service. To achieve this you should subclass or instantiate the `services.hooks.UrlHook` class and then register the URL patterns with the hook.
|
||||
|
||||
@@ -10,7 +10,7 @@ Auto groups allows you to automatically place users of certain states into Corp
|
||||
|
||||
## Installation
|
||||
|
||||
Add `allianceauth.eveonline.autogroups` to your `INSTALLED_APPS` and run migrations. All other settings are controlled via the admin panel under the `Eve_Autogroups` section.
|
||||
Add `'allianceauth.eveonline.autogroups',` to your `INSTALLED_APPS` list and run migrations. All other settings are controlled via the admin panel under the `Eve_Autogroups` section.
|
||||
|
||||
|
||||
## Configuring a group
|
||||
|
||||
@@ -4,11 +4,9 @@ This module is used to check the registration status of corp members and to dete
|
||||
|
||||
## Installation
|
||||
|
||||
Add `allianceauth.corputils` to your `INSTALLED_APPS` setting and run migrations. In `myauth/settings/local.py`:
|
||||
Corp Stats requires access to the `esi-corporations.read_corporation_membership.v1` SSO scope. Update your application on the [EVE Developers site](https://developers.eveonline.com) to ensure it is available.
|
||||
|
||||
INSTALLED_APPS += ['allianceauth.corputils']
|
||||
|
||||
Run migrations to complete installation.
|
||||
Add `'allianceauth.corputils',` to your `INSTALLED_APPS` list in your auth project's settings file. Run migrations to complete installation.
|
||||
|
||||
## Creating a Corp Stats
|
||||
|
||||
@@ -43,9 +41,7 @@ On the right of this bar is a search field. Press enter to search. It checks all
|
||||
|
||||

|
||||
|
||||
Corp Stats automatically update every 6 hours. An update can be performed immediately by pressing thi update button.
|
||||
|
||||
Only superusers and the creator of the Corp Stat can trigger an immediate update.
|
||||
An update can be performed immediately by pressing the update button. Anyone who can view the Corp Stats can update it.
|
||||
|
||||
### Character Lists
|
||||
|
||||
@@ -114,6 +110,16 @@ To use this feature, users will require some of the following:
|
||||
|
||||
Users who add a Corp Stats with their token will be granted permissions to view it regardless of the above permissions. View permissions are interpreted in the "OR" sense: a user can view their corp's Corp Stats without the `view_corp_corpstats` permission if they have the `view_alliance_corpstats` permission, same idea for their state. Note that these evaluate against the user's main character.
|
||||
|
||||
## Automatic Updating
|
||||
By default Corp Stats are only updated on demand. If you want to automatically refresh on a schedule, add an entry to your project's settings file:
|
||||
|
||||
CELERYBEAT_SCHEDULE['update_all_corpstats'] = {
|
||||
'task': 'allianceauth.corputils.tasks.update_all_corpstats',
|
||||
'schedule': crontab(minute=0, hour="*/6"),
|
||||
},
|
||||
|
||||
Adjust the crontab as desired.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Failure to create Corp Stats
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
## Installation
|
||||
|
||||
Add `allianceauth.fleetactivitytracking` to your `INSTALLED_APPS` setting. In `myauth/settings/local.py`:
|
||||
Fleet Activity Tracking requires access to the `esi-location.read_location.v1`, `esi-location.read_ship_type.v1`, and `esi-universe.read_structures.v1` SSO scopes. Update your application on the [EVE Developers site](https://developers.eveonline.com) to ensure these are available.
|
||||
|
||||
INSTALLED_APPS += ['allianceauth.fleetactivitytracking']
|
||||
|
||||
Run migrations to complete installation.
|
||||
Add `'allianceauth.fleetactivitytracking',` to your `INSTALLED_APPS` list in your auth project's settings file. Run migrations to complete installation.
|
||||
@@ -2,6 +2,17 @@
|
||||
|
||||
## Installation
|
||||
|
||||
Add `allianceauth.fleetup` to your `INSTALLED_APPS` setting. In `myauth/settings/local.py`:
|
||||
Add `'allianceauth.fleetup',` to your auth project's `INSTALLED_APPS` list.
|
||||
|
||||
INSTALLED_APPS += ['allianceauth.fleetup']
|
||||
Additional settings are required. Append the following settings to the end of your auth project's settings file and fill them out.
|
||||
|
||||
FLEETUP_APP_KEY = '' # The app key from http://fleet-up.com/Api/MyApps
|
||||
FLEETUP_USER_ID = '' # The user id from http://fleet-up.com/Api/MyKeys
|
||||
FLEETUP_API_ID = '' # The API id from http://fleet-up.com/Api/MyKeys
|
||||
FLEETUP_GROUP_ID = '' # The id of the group you want to pull data from, see http://fleet-up.com/Api/Endpoints#groups_mygroupmemberships
|
||||
|
||||
Once filled out restart gunicorn and celery.
|
||||
|
||||
## Permissions
|
||||
|
||||
The Fleetup module is only visible to users with the `auth | user | view_fleeup` permission.
|
||||
@@ -5,7 +5,7 @@ Group Management is one of the core tasks of Alliance Auth. Many of Alliance Aut
|
||||
|
||||
Administrators can create custom groups for users to join. Examples might be groups like `Leadership`, `CEO` or `Scouts`.
|
||||
|
||||
When you create a Django `Group`, Auth automatically creates a corresponding `AuthGroup` model. The admin page looks like this:
|
||||
When you create a `Group` additional settings are available beyond the normal Django group model. The admin page looks like this:
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -2,11 +2,7 @@
|
||||
|
||||
## Installation
|
||||
|
||||
Add `allianceauth.hrapplications` to your `INSTALLED_APPS` setting. In `myauth/settings/local.py`:
|
||||
|
||||
INSTALLED_APPS += ['allianceauth.hrapplications']
|
||||
|
||||
Run migrations to complete installation.
|
||||
Add `'allianceauth.hrapplications',` to your `INSTALLED_APPS` list in your auth project's settings file. Run migrations to complete installation.
|
||||
|
||||
## Management
|
||||
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
:maxdepth: 1
|
||||
:caption: Features Contents
|
||||
|
||||
hrapplications
|
||||
corpstats
|
||||
states
|
||||
groups
|
||||
autogroups
|
||||
hrapplications
|
||||
corpstats
|
||||
permissions_tool
|
||||
nameformats
|
||||
fleetup
|
||||
|
||||
@@ -2,8 +2,4 @@
|
||||
|
||||
## Installation
|
||||
|
||||
Add `allianceauth.optimer` to your `INSTALLED_APPS` setting. In `myauth/settings/local.py`:
|
||||
|
||||
INSTALLED_APPS += ['allianceauth.optimer']
|
||||
|
||||
Run migrations to complete installation.
|
||||
Add `'allianceauth.optimer',` to your `INSTALLED_APPS` list in your auth project's settings file. Run migrations to complete installation.
|
||||
@@ -9,9 +9,7 @@ Access to most of Alliance Auth's features are controlled by Django's permission
|
||||
|
||||
## Installation
|
||||
|
||||
Add `allianceauth.permissions_tool` to your `INSTALLED_APPS` setting. In `myauth/settings/local.py`:
|
||||
|
||||
INSTALLED_APPS += ['allianceauth.permissions_tool']
|
||||
Add `'allianceauth.permissions_tool',` to your `INSTALLED_APPS` list in your auth project's settings file.
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
@@ -2,8 +2,4 @@
|
||||
|
||||
## Installation
|
||||
|
||||
Add `allianceauth.srp` to your `INSTALLED_APPS` setting. In `myauth/settings/local.py`:
|
||||
|
||||
INSTALLED_APPS += ['allianceauth.srp']
|
||||
|
||||
Run migrations to complete installation.
|
||||
Add `'allianceauth.srp',` to your `INSTALLED_APPS` list in your auth project's settings file. Run migrations to complete installation.
|
||||
49
docs/features/states.md
Normal file
49
docs/features/states.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# The State System
|
||||
|
||||
## Overview
|
||||
|
||||
In Alliance Auth v1 admins were able to define which corporations and alliances were to be considered "members" with full permissions and "blues" with restricted permissions. The state system is the replacement for these static definitions: admins can now create as many states as desired, as well as extend membership to specific characters.
|
||||
|
||||
## Creating a State
|
||||
States are created through your installation's admin site. Upon install three states are created for you: `Member`, `Blue`, and `Guest`. New ones can be created like any other Django model by users with the appropriate permission (`authentication | state | Can add state`) or superusers.
|
||||
|
||||
A number of fields are available and are described below.
|
||||
|
||||
### Name
|
||||
This is the displayed name of a state. Should be self-explanatory.
|
||||
|
||||
### Permissions
|
||||
This lets you select permissions to grant to the entire state, much like a group. Any user with this state will be granted these permissions.
|
||||
|
||||
A common use case would be granting service access to a state.
|
||||
|
||||
### Priority
|
||||
This value determines the order in which states are applied to users. Higher numbers come first. So if a random user `Bob` could member of both the `Member` and `Blue` states, because `Member` has a higher priority `Bob` will be assigned to it.
|
||||
|
||||
### Public
|
||||
Checking this box means this state is available to all users. There isn't much use for this outside the `Guest` state.
|
||||
|
||||
### Member Characters
|
||||
This lets you select which characters the state is available to. Characters can be added by selecting the green plus icon.
|
||||
|
||||
### Member Corporations
|
||||
This lets you select which corporations the state is available to. Corporations can be added by selecting the green plus icon.
|
||||
|
||||
### Member Alliances
|
||||
This lets you select which alliances the state is available to. Alliances can be added by selecting the gree plus icon.
|
||||
|
||||
## Determining a User's State
|
||||
States are mutually exclusive, meaning a user can only be in one at a time.
|
||||
|
||||
Membership is determined based on a user's main character. States are tested in order of descending priority - the first one which allows membership to the main character is assigned to the user.
|
||||
|
||||
States are automatically assigned when a user registers to the site, their main character changes, they are activated or deactivated, or states are edited. Note that editing states triggers lots of state checks so it can be a very slow process.
|
||||
|
||||
Assigned states are visible in the `Users` section of the `Authentication` admin site.
|
||||
|
||||
## The Guest State
|
||||
If no states are available to a user's main character, or their account has been deactivated, they are assigned to a catch-all `Guest` state. This state cannot be deleted nor can its name be changed.
|
||||
|
||||
The `Guest` state allows permissions to be granted to users who would otherwise not get any. For example access to public services can be granted by giving the `Guest` state a service access permission.
|
||||
|
||||
|
||||
@@ -2,8 +2,4 @@
|
||||
|
||||
## Installation
|
||||
|
||||
Add `allianceauth.timerboard` to your `INSTALLED_APPS` setting. In `myauth/settings/local.py`:
|
||||
|
||||
INSTALLED_APPS += ['allianceauth.timerboard']
|
||||
|
||||
Run migrations to complete installation.
|
||||
Add `'allianceauth.timerboard',` to your `INSTALLED_APPS` list in your auth project's settings file. Run migrations to complete installation.
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
# Alliance Auth
|
||||
|
||||
Alliance service auth to help large scale alliances manage services. Built for "The 99 Percent" open for anyone to use
|
||||
An auth system for EVE Online to help in-game organizations manage online service access.
|
||||
|
||||
# Installing
|
||||
|
||||
|
||||
@@ -66,16 +66,22 @@ CentOS:
|
||||
Alliance Auth needs a MySQL user account and database. Open an SQL shell with `mysql -u root -p` and create them as follows, replacing `PASSWORD` with an actual secure password:
|
||||
|
||||
CREATE USER 'allianceserver'@'localhost' IDENTIFIED BY 'PASSWORD';
|
||||
CREATE DATABASE alliance_auth;
|
||||
CREATE DATABASE alliance_auth CHARACTER SET utf8;
|
||||
GRANT ALL PRIVILEGES ON alliance_auth . * TO 'allianceserver'@'localhost';
|
||||
|
||||
Close the SQL shell and secure your database server with the `mysql_secure_installation` command.
|
||||
|
||||
If you're updating from v1, populate this database with a copy of the data from your v1 database.
|
||||
|
||||
mysqldump -u root -p v1_database_name_here | mysql -u root -p alliance_auth
|
||||
|
||||
Note this command will prompt you for the root password twice.
|
||||
|
||||
## Auth Install
|
||||
|
||||
### User Account
|
||||
|
||||
For security and permissions, it’s highly recommended you create a separate user to install under.
|
||||
For security and permissions, it’s highly recommended you create a separate user to install auth under. Do not log in as this account.
|
||||
|
||||
Ubuntu:
|
||||
|
||||
@@ -109,6 +115,8 @@ Activate the virtualenv using `source /home/allianceserver/venv/auth/bin/activat
|
||||
Each time you come to do maintenance on your Alliance Auth installation, you should activate your virtual environment first. When finished, deactivate it with the 'deactivate' command.
|
||||
```
|
||||
|
||||
Ensure wheel is available with `pip install wheel` before continuing.
|
||||
|
||||
### Alliance Auth Project
|
||||
|
||||
You can install the library using `pip install allianceauth`. This will install Alliance Auth and all its python dependencies. You should also install gunicorn with `pip install gunicorn` before proceeding.
|
||||
@@ -127,7 +135,6 @@ Now we need to round up all the static files required to render templates. Make
|
||||
|
||||
mkdir -p /var/www/myauth/static
|
||||
python /home/allianceserver/myauth/manage.py collectstatic
|
||||
chown -R www-data:www-data /var/www/myauth/static
|
||||
|
||||
Check to ensure your settings are valid.
|
||||
|
||||
@@ -163,11 +170,11 @@ Once installed it needs a configuration file to know which processes to watch. Y
|
||||
|
||||
Ubuntu:
|
||||
|
||||
ln /home/allianceserver/myauth/supervisor.conf /etc/supervisor/conf.d/myauth.conf
|
||||
ln -s /home/allianceserver/myauth/supervisor.conf /etc/supervisor/conf.d/myauth.conf
|
||||
|
||||
CentOS:
|
||||
|
||||
ln /home/allianceserver/myauth/supervisor.conf /etc/supervisord.d/myauth.ini
|
||||
ln -s /home/allianceserver/myauth/supervisor.conf /etc/supervisord.d/myauth.ini
|
||||
|
||||
And activate it with `supervisorctl reload`.
|
||||
|
||||
@@ -190,10 +197,9 @@ Before using your auth site it is essential to create a superuser account. This
|
||||
|
||||
python /home/allianceserver/myauth/manage.py createsuperuser
|
||||
|
||||
```eval_rst
|
||||
.. important::
|
||||
Be sure to add a main character to this account before attempting to activate services with it.
|
||||
```
|
||||
The superuser account is accessed by logging in via the admin site at `https://example.com/admin`.
|
||||
|
||||
If you intend to use this account as your personal auth account you need to add a main character. Navigate to the normal user dashboard (at `https://example.com`) after logging in via the admin site and select `Change Main`. Once a main character has been added it is possible to use SSO to login to this account.
|
||||
|
||||
## Updating
|
||||
|
||||
@@ -203,4 +209,4 @@ Some releases come with changes to settings: update your project's settings with
|
||||
|
||||
Some releases come with new or changed models. Update your database to reflect this with `python /home/allianceserver/myauth/manage.py migrate`.
|
||||
|
||||
Always restart celery and gunicorn after updating.
|
||||
Always restart celery and gunicorn after updating.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Apache Setup
|
||||
# Apache
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -20,6 +20,8 @@ CentOS:
|
||||
|
||||
## Configuration
|
||||
|
||||
Apache needs to be able to read the folder containing your auth project's static files. On Ubuntu: `chown -R www-data:www-data /var/www/myauth/static`, and on CentOS: `chown -R apache:apache /var/www/myauth/static`
|
||||
|
||||
Apache serves sites through defined virtual hosts. These are located in `/etc/apache2/sites-available/` on Ubuntu and `/etc/httpd/conf.d/httpd.conf` on CentOS.
|
||||
|
||||
A virtual host for auth need only proxy requests to your WSGI server (gunicorn if you followed the install guide) and serve static files. Examples can be found below. Create your config in its own file eg `myauth.conf`.
|
||||
@@ -57,13 +59,13 @@ Place your virtual host configuration in the appropriate section within `/etc/ht
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
If you use SSL, add the following lines inside the `VirtualHost` block:
|
||||
## SSL
|
||||
|
||||
It's 2018 - there's no reason to run a site without SSL. The EFF provides free, renewable SSL certificates with an automated installer. Visit their [website](https://certbot.eff.org/) for information.
|
||||
|
||||
After acquiring SSL the config file needs to be adjusted. Add the following lines inside the `<VirtualHost>` block:
|
||||
|
||||
```
|
||||
RequestHeader set X-FORWARDED-PROTOCOL https
|
||||
RequestHeader set X-FORWARDED-SSL On
|
||||
```
|
||||
|
||||
## SSL
|
||||
|
||||
It's 2018 - there's no reason to run a site without SSL. The EFF provides free, renewable SSL certificates with an automated installer. Visit their [website](https://certbot.eff.org/) for information.
|
||||
@@ -61,7 +61,7 @@ Change it by adding `--workers=2` to the command.
|
||||
##### Running with a virtual environment
|
||||
If you're running with a virtual environment, you'll need to add the path to the `command=` config line.
|
||||
|
||||
e.g. `command=/path/to/venv/bin/gunicorn alliance_auth.wsgi`
|
||||
e.g. `command=/path/to/venv/bin/gunicorn myauth.wsgi`
|
||||
|
||||
### Starting via Supervisor
|
||||
|
||||
@@ -70,48 +70,6 @@ Once you have your configuration all sorted, you will need to reload your superv
|
||||
|
||||
## Configuring your webserver
|
||||
|
||||
### NGINX
|
||||
To your server config add:
|
||||
|
||||
```
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
proxy_read_timeout 90;
|
||||
proxy_redirect http://127.0.0.1:8000/ http://$host/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
```
|
||||
|
||||
Set `proxy_pass` and `proxy_redirect` to the address you set under `--bind=`. Set the second part of `proxy_redirect` to the URL you're hosting services on. Tell NGINX to reload your config, job done. Enjoy your lower memory usage and better performance!
|
||||
|
||||
If PHP is stopping you moving to NGINX, check out php-fpm as a way to run your PHP applications.
|
||||
|
||||
### Apache
|
||||
If you were using mod_wsgi before, make a backup of your old config first and then strip out all of the mod_wsgi config from your Apache VirtualHost first config.
|
||||
|
||||
Your config will need something along these lines:
|
||||
```
|
||||
ProxyPreserveHost On
|
||||
<Location />
|
||||
SSLRequireSSL
|
||||
ProxyPass http://127.0.0.1:8000/
|
||||
ProxyPassReverse http://127.0.0.1:8000/
|
||||
RequestHeader set X-FORWARDED-PROTOCOL ssl
|
||||
RequestHeader set X-FORWARDED-SSL on
|
||||
</Location>
|
||||
```
|
||||
|
||||
Set `ProxyPass` and `ProxyPassReverse` addresses to your `--bind=` address set earlier.
|
||||
|
||||
You will need to enable some Apache mods. `sudo a2enmod http_proxy` should take care of the dependencies.
|
||||
|
||||
Restart Apache and you should be done.
|
||||
|
||||
### Other web servers
|
||||
|
||||
Any web server capable of proxy passing should be able to sit in front of Gunicorn. Consult their documentation armed with your `--bind=` address and you should be able to find how to do it relatively easy.
|
||||
|
||||
|
||||
|
||||
@@ -31,14 +31,25 @@ Your .htaccess files wont work. Nginx has a separate way of managing access to f
|
||||
|
||||
Install Nginx via your preferred package manager or other method. If you need help just search, there are plenty of guides on installing Nginx out there.
|
||||
|
||||
Nginx needs to be able to read the folder containing your auth project's static files. On Ubuntu: `chown -R nginx:nginx /var/www/myauth/static`, and on CentOS: `chown -R nginx:nginx /var/www/myauth/static`
|
||||
|
||||
You will need to have [Gunicorn](gunicorn.md) or some other WSGI server setup for hosting Alliance Auth.
|
||||
|
||||
Create a config file in `/etc/nginx/sites-available` call it `alliance-auth.conf` or whatever your preferred name is and copy the basic config in. Make whatever changes you feel are necessary.
|
||||
### Ubuntu
|
||||
Create a config file in `/etc/nginx/sites-available` and call it `alliance-auth.conf` or whatever your preferred name is.
|
||||
|
||||
Create a symbolic link to enable the site `ln -s /etc/nginx/sites-available/alliance-auth.conf /etc/nginx/sites-enabled/`
|
||||
|
||||
### CentOS
|
||||
|
||||
Create a config file in `/etc/nginx/conf.d` and call it `alliance-auth.conf` or whatever your preferred name is.
|
||||
|
||||
Create a symbolic link to enable the site `sudo ln -s /etc/nginx/sites-available/alliance-auth.conf /etc/nginx/sites-enabled/` and then reload Nginx for the config to take effect, `sudo service nginx reload` for Ubuntu.
|
||||
|
||||
### Basic config
|
||||
|
||||
Copy this basic config into your config file. Make whatever changes you feel are necessary.
|
||||
|
||||
|
||||
```
|
||||
server {
|
||||
listen 80;
|
||||
@@ -59,9 +70,11 @@ server {
|
||||
}
|
||||
```
|
||||
|
||||
Restart Nginx after making changes to the config files. On Ubuntu `service nginx restart` and on CentOS `systemctl restart nginx.service`.
|
||||
|
||||
#### Adding TLS/SSL
|
||||
|
||||
With [Let's Encrypt](https://letsencrypt.org/) offering free SSL certificates, there's no good reason to not run HTTPS anymore.
|
||||
With [Let's Encrypt](https://letsencrypt.org/) offering free SSL certificates, there's no good reason to not run HTTPS anymore. The bot can automatically configure Nginx on some operating systems. If not proceed with the manual steps below.
|
||||
|
||||
Your config will need a few additions once you've got your certificate.
|
||||
|
||||
|
||||
@@ -4,10 +4,22 @@ Discord is a web-based instant messaging client with voice. Kind of like teamspe
|
||||
|
||||
## Setup
|
||||
|
||||
Add `allianceauth.services.modules.discord` to your `INSTALLED_APPS` list and run migrations before continuing with this guide to ensure the service is installed.
|
||||
### Prepare Your Settings File
|
||||
In your auth project's settings file, do the following:
|
||||
- Add `'allianceauth.services.modules.discord',` to your `INSTALLED_APPS` list
|
||||
- Append the following to the bottom of the settings file:
|
||||
|
||||
|
||||
# Discord Configuration
|
||||
DISCORD_GUILD_ID = ''
|
||||
DISCORD_CALLBACK_URL = ''
|
||||
DISCORD_APP_ID = ''
|
||||
DISCORD_APP_SECRET = ''
|
||||
DISCORD_BOT_TOKEN = ''
|
||||
DISCORD_SYNC_NAMES = False
|
||||
|
||||
### Creating a Server
|
||||
*If you already have a Discord server, skip the creation step, but be sure to retrieve the server ID and enter it in settings.py*
|
||||
*If you already have a Discord server, skip the creation step, but be sure to retrieve the server ID*
|
||||
|
||||
Navigate to the [Discord site](https://discordapp.com/) and register an account, or log in if you have one already.
|
||||
|
||||
@@ -19,14 +31,7 @@ Now retrieve the server ID from the URL of the page you’re on. The ID is the f
|
||||
|
||||
with a server ID of `120631096835571712`
|
||||
|
||||
Update settings.py, inputting the server ID as `DISCORD_GUILD_ID`
|
||||
|
||||
### Generating an Invite
|
||||
Still on the Discord site, in your new server, an invite needs to be generated for users to join. If you with for users to initially join a different channel than `#general`, create it and follow the steps below, substituting this channel for `#general`.
|
||||
|
||||
On the left bar under the Text Channels heading, hover over `#general` on the right site. There are two icons, a box with an arrow and a gear. Press the box, then on the bottom left select Advanced Settings. Set the expiration to never, and no limit on uses. Press generate.
|
||||
|
||||
This returns a code that looks like `https://discord.gg/0fmA8MyXV6qt7XAZ`. The part after the last slash, `0fmA8MyXV6qt7XAZ`, is the invite code. Update settings.py, inputting this invite code as `DISCORD_INVITE_CODE`
|
||||
Update your auth project's settings file, inputting the server ID as `DISCORD_GUILD_ID`
|
||||
|
||||
### Registering an Application
|
||||
|
||||
@@ -34,15 +39,18 @@ Navigate to the [Discord Developers site.](https://discordapp.com/developers/app
|
||||
|
||||
Give it a name and description relating to your auth site. Add a redirect to `https://example.com/discord/callback/`, substituting your domain. Press Create Application.
|
||||
|
||||
Update settings.py, inputting this redirect address as `DISCORD_CALLBACK_URL`
|
||||
Update your auth project's settings file, inputting this redirect address as `DISCORD_CALLBACK_URL`
|
||||
|
||||
On the application summary page, press Create a Bot User.
|
||||
|
||||
Update settings.py with these pieces of information from the summary page:
|
||||
Update your auth project's settings file with these pieces of information from the summary page:
|
||||
- From the App Details panel, `DISCORD_APP_ID` is the Client/Application ID
|
||||
- From the App Details panel, `DISCORD_APP_SECRET` is the Secret
|
||||
- From the App Bot Users panel, `DISCORD_BOT_TOKEN` is the Token
|
||||
|
||||
### Preparing Auth
|
||||
Before continuing it is essential to run migrations and restart gunicorn and celery.
|
||||
|
||||
### Adding a Bot to the Server
|
||||
Once created, navigate to the services page of your AllianceAuth install as the superuser account. At the top there is a big green button labelled Link Discord Server. Click it, then from the drop down select the server you created, and then Authorize.
|
||||
|
||||
@@ -50,8 +58,13 @@ This adds a new user to your Discord server with a `BOT` tag, and a new role wit
|
||||
|
||||
To manage roles, this bot role must be at the top of the hierarchy. Edit your Discord server, roles, and click and drag the role with the same name as your application to the top of the list. This role must stay at the top of the list for the bot to work. Finally, the owner of the bot account must enable 2 Factor Authentication (this is required from discord for kicking and modifying member roles). If you are unsure what 2FA is or how to set it up, refer to [this support page](https://support.discordapp.com/hc/en-us/articles/219576828). It is also recommended to force 2fa on your server (this forces any admins or moderators to have 2fa enabled to perform similar functions on discord).
|
||||
|
||||
Note that the bot will never appear online as it does not participate in chat channels.
|
||||
|
||||
### Linking Accounts
|
||||
Instead of the usual account creation procedure, for Discord to work we need to link accounts to AllianceAuth. When attempting to enable the Discord service, users are redirected to the official Discord site to authenticate. They will need to create an account if they don't have one prior to continuing. Upon authorization, users are redirected back to AllianceAuth with an OAuth code which is used to join the Discord server.
|
||||
|
||||
### Syncing Nicknames
|
||||
If you want users to have their Discord nickname changed to their in-game character name, set `DISCORD_SYNC_NAMES` to `True`
|
||||
|
||||
## Managing Roles
|
||||
Once users link their accounts you’ll notice Roles get populated on Discord. These are the equivalent to Groups on every other service. The default permissions should be enough for members to chat and use comms. Add more permissions to the roles as desired through the server management window.
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
# Discourse
|
||||
|
||||
Add `allianceauth.services.modules.discourse` to your `INSTALLED_APPS` list and run migrations before continuing with this guide to ensure the service is installed.
|
||||
## Prepare Your Settings
|
||||
In your auth project's settings file, do the following:
|
||||
- Add `'allianceauth.services.modules.discourse',` to your `INSTALLED_APPS` list
|
||||
- Append the following to your local.py settings file:
|
||||
|
||||
|
||||
# Discourse Configuration
|
||||
DISCOURSE_URL = ''
|
||||
DISCOURSE_API_USERNAME = ''
|
||||
DISCOURSE_API_KEY = ''
|
||||
DISCOURSE_SSO_SECRET = ''
|
||||
|
||||
|
||||
## Install Docker
|
||||
|
||||
@@ -65,13 +76,11 @@ Now build:
|
||||
sudo ./launcher bootstrap app
|
||||
sudo ./launcher start app
|
||||
|
||||
## Apache config
|
||||
## Web Server Configuration
|
||||
|
||||
Discourse must run on its own subdomain - it can't handle routing behind an alias like '/forums'. To do so, make a new apache config:
|
||||
You will need to configure your web server to proxy requests to Discourse.
|
||||
|
||||
sudo nano /etc/apache2/sites-available/discourse.conf
|
||||
|
||||
And enter the following, changing the port if you used a different number:
|
||||
A minimal apache config might look like:
|
||||
|
||||
<VirtualHost *:80>
|
||||
ServerName discourse.example.com
|
||||
@@ -79,10 +88,16 @@ And enter the following, changing the port if you used a different number:
|
||||
ProxyPassReverse / http://0.0.0.0:7890/
|
||||
</VirtualHost>
|
||||
|
||||
Now enable proxies and restart apache:
|
||||
A minimal nginx config might look like:
|
||||
|
||||
sudo a2enmod proxy_http
|
||||
sudo service apache2 reload
|
||||
server {
|
||||
listen 80;
|
||||
server_name discourse.example.com;
|
||||
location / {
|
||||
include proxy_params;
|
||||
proxy_pass http://127.0.0.1:7890;
|
||||
}
|
||||
}
|
||||
|
||||
## Configure API
|
||||
|
||||
@@ -99,12 +114,8 @@ Follow prompts, being sure to answer `y` when asked to allow admin privileges.
|
||||
|
||||
Navigate to `discourse.example.com` and log on. Top right press the 3 lines and select `Admin`. Go to API tab and press `Generate Master API Key`.
|
||||
|
||||
Now go to the allianceauth folder and edit settings:
|
||||
|
||||
nano /home/allianceserver/allianceauth/alliance_auth/settings.py
|
||||
|
||||
Scroll down to the Discourse section and set the following:
|
||||
- `DISCOURSE_URL`: `discourse.example.com`
|
||||
Add the following values to your auth project's settings file:
|
||||
- `DISCOURSE_URL`: `discourse.example.com` (do not add a trailing slash!)
|
||||
- `DISCOURSE_API_USERNAME`: the username of the admin account you generated the API key with
|
||||
- `DISCOURSE_API_KEY`: the key you just generated
|
||||
|
||||
@@ -115,11 +126,6 @@ Navigate to `discourse.example.com` and log in. Back to the admin site, scroll d
|
||||
- `sso_url`: `http://example.com/discourse/sso`
|
||||
- `sso_secret`: some secure key
|
||||
|
||||
Save, now change settings.py and add the following:
|
||||
- `DISCOURSE_SSO_SECRET`: the secure key you just set
|
||||
Save, now set `DISCOURSE_SSO_SECRET` in your auth project's settings file to the secure key you just put in Discourse.
|
||||
|
||||
### Enable for your members
|
||||
|
||||
Set either or both of `ENABLE_AUTH_DISCOURSE` and `ENABLE_BLUE_DISCOURSE` in settings.py for your members to gain access. Save and exit with control+o, enter, control+x.
|
||||
|
||||
## Done
|
||||
Finally run migrations and restart gunicorn and celery.
|
||||
|
||||
@@ -1,11 +1,31 @@
|
||||
# Alliance Market
|
||||
|
||||
Add `allianceauth.services.modules.market` to your `INSTALLED_APPS` list and run migrations before continuing with this guide to ensure the service is installed.
|
||||
## Dependencies
|
||||
Alliance Market requires php installed in your web server. Apache has `mod_php`, NGINX requires `php-fpm`.
|
||||
|
||||
## Prepare Your Settings
|
||||
In your auth project's settings file, do the following:
|
||||
- Add `'allianceauth.services.modules.market',` to your `INSTALLED_APPS` list
|
||||
- Append the following to the bottom of the settings file
|
||||
|
||||
|
||||
# Alliance Market
|
||||
MARKET_URL = ''
|
||||
DATABASES['market'] = {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'NAME': 'alliance_market',
|
||||
'USER': 'allianceserver-market',
|
||||
'PASSWORD': 'password',
|
||||
'HOST': '127.0.0.1',
|
||||
'PORT': '3306',
|
||||
}
|
||||
|
||||
## Setup Alliance Market
|
||||
Alliance Market needs a database. Create one in mysql. Default name is `alliance_market`:
|
||||
|
||||
mysql -u root -p
|
||||
create database alliance_market;
|
||||
grant all privileges on alliance_market . * to 'allianceserver'@'localhost';
|
||||
exit;
|
||||
|
||||
To clone the repo, install packages:
|
||||
@@ -79,23 +99,37 @@ Install SDE:
|
||||
|
||||
sudo php app/console evernus:update:sde
|
||||
|
||||
Edit your apache config. Add the following:
|
||||
Configure your web server to serve alliance market.
|
||||
|
||||
Alias /market /var/www/evernus-alliance-market/web/
|
||||
A minimal apache config might look like:
|
||||
|
||||
<Directory "/var/www/evernus-alliance-market/web/">
|
||||
DirectoryIndex app.php
|
||||
Require all granted
|
||||
AllowOverride all
|
||||
</Directory>
|
||||
<VirtualHost *:80>
|
||||
ServerName market.example.com
|
||||
DocumentRoot /var/www/evernus-alliance-market/web
|
||||
<Directory "/var/www/evernus-alliance-market/web/">
|
||||
DirectoryIndex app.php
|
||||
Require all granted
|
||||
AllowOverride all
|
||||
</Directory>
|
||||
</VirtualHost>
|
||||
|
||||
Enable rewriting
|
||||
A minimal nginx config might look like:
|
||||
|
||||
sudo a2enmod rewrite
|
||||
|
||||
Restart apache
|
||||
|
||||
sudo service apache2 reload
|
||||
server {
|
||||
listen 80;
|
||||
server_name market.example.com;
|
||||
root /var/www/evernus-alliance-market/web;
|
||||
index app.php;
|
||||
access_log /var/logs/market.access.log;
|
||||
|
||||
location ~ \.php$ {
|
||||
try_files $uri =404;
|
||||
fastcgi_pass unix:/tmp/php.socket;
|
||||
fastcgi_index index.php;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
include fastcgi_params;
|
||||
}
|
||||
}
|
||||
|
||||
Once again, set cache permissions:
|
||||
|
||||
@@ -104,3 +138,7 @@ Once again, set cache permissions:
|
||||
Add a user account through auth, then make it a superuser:
|
||||
|
||||
sudo php app/console fos:user:promote your_username --super
|
||||
|
||||
Now edit your auth project's settings file and fill in the web URL to your market as well as the database details.
|
||||
|
||||
Finally run migrations and restart gunicorn and celery.
|
||||
@@ -1,6 +1,13 @@
|
||||
# Mumble
|
||||
|
||||
Add `allianceauth.services.modules.mumble` to your `INSTALLED_APPS` list and run migrations before continuing with this guide to ensure the service is installed.
|
||||
## Prepare Your Settings
|
||||
In your auth project's settings file, do the following:
|
||||
- Add `'allianceauth.services.modules.mumble',` to your `INSTALLED_APPS` list
|
||||
- Append the following to your local.py settings file:
|
||||
|
||||
|
||||
# Mumble Configuration
|
||||
MUMBLE_URL = ""
|
||||
|
||||
## Overview
|
||||
Mumble is a free voice chat server. While not as flashy as teamspeak, it has all the functionality and is easier to customize. And is better. I may be slightly biased.
|
||||
@@ -14,7 +21,7 @@ The mumble server package can be retrieved from a repository we need to add, mum
|
||||
Now two packages need to be installed:
|
||||
|
||||
sudo apt-get install python-software-properties mumble-server
|
||||
|
||||
|
||||
Download the appropriate authenticator release from https://github.com/allianceauth/mumble-authenticator and install the python dependencies for it:
|
||||
|
||||
pip install -r requirements.txt
|
||||
@@ -80,5 +87,7 @@ Note that groups will only be created on Mumble automatically when a user joins
|
||||
## Making and Managing Channels
|
||||
ACL is really above the scope of this guide. Once AllianceAuth creates your groups, go ahead and follow one of the wonderful web guides available on how to set up channel ACL properly.
|
||||
|
||||
## Setup Complete
|
||||
You’ve finished the steps required to make AllianceAuth work with Mumble. Play around with it and make it your own.
|
||||
## Prepare Auth
|
||||
In your project's settings file, set `MUMBLE_URL` to the public address of your mumble server. Do not include any leading `http://` or `mumble://`.
|
||||
|
||||
Run migrations and restart gunicorn and celery to complete setup.
|
||||
@@ -1,17 +1,38 @@
|
||||
# Openfire
|
||||
|
||||
Add `allianceauth.services.modules.openfire` to your `INSTALLED_APPS` list and run migrations before continuing with this guide to ensure the service is installed.
|
||||
Openfire is a jabber (XMPP) server.
|
||||
|
||||
## Prepare Your Settings
|
||||
- Add `'allianceauth.services.modules.openfire',` to your `INSTALLED_APPS` list
|
||||
- Append the following to your auth project's settings file:
|
||||
|
||||
|
||||
# Jabber Configuration
|
||||
JABBER_URL = ""
|
||||
JABBER_PORT = 5223
|
||||
JABBER_SERVER = ""
|
||||
OPENFIRE_ADDRESS = ""
|
||||
OPENFIRE_SECRET_KEY = ""
|
||||
BROADCAST_USER = ""
|
||||
BROADCAST_USER_PASSWORD = ""
|
||||
BROADCAST_SERVICE_NAME = "broadcast"
|
||||
|
||||
## Overview
|
||||
Openfire is a java-based xmpp server (jabber).
|
||||
|
||||
## Dependencies
|
||||
One additional package is required - [openjdk8](http://askubuntu.com/questions/464755/how-to-install-openjdk-8-on-14-04-lts)
|
||||
One additional package is required - openjdk8
|
||||
|
||||
Ubuntu:
|
||||
|
||||
sudo add-apt-repository ppa:webupd8team/java -y
|
||||
sudo apt-get update
|
||||
sudo apt-get install oracle-java8-installer
|
||||
|
||||
CentOS:
|
||||
|
||||
sudo yum -y install java-1.8.0-openjdk java-1.8.0-openjdk-devel
|
||||
|
||||
## Setup
|
||||
### Download Installer
|
||||
Openfire is not available through repositories so we need to get a debian from the developer.
|
||||
@@ -30,6 +51,14 @@ Now install from the debian. Replace the filename with your file name (the last
|
||||
|
||||
sudo dpkg -i openfire_4.1.1_all.deb
|
||||
|
||||
### Create Database
|
||||
Performance is best when working from a SQL database. If you installed MySQL or MariaDB alongside your auth project, go ahead and create a database for openfire:
|
||||
|
||||
mysql -u root -p
|
||||
create database alliance_jabber;
|
||||
grant all privileges on alliance_jabber . * to 'allianceserver'@'localhost';
|
||||
exit;
|
||||
|
||||
### Web Configuration
|
||||
The remainder of the setup occurs through Openfire’s web interface. Navigate to http://example.com:9090, or if you’re behind CloudFlare, go straight to your server’s IP:9090.
|
||||
|
||||
@@ -42,17 +71,22 @@ Under Database Settings, select `Standard Database Connection`
|
||||
On the next page, select `MySQL` from the dropdown list and change the following:
|
||||
- `[server]` is replaced by `127.0.0.1`
|
||||
- `[database]` is replaced by the name of the database to be used by Openfire
|
||||
- enter the MySQL username you created for AllianceAuth, usually `allianceserver`
|
||||
- enter the MySQL password for this user
|
||||
- enter the login details for your auth project's database user
|
||||
|
||||
If Openfire returns with a failed to connect error, re-check these settings. Note the lack of square brackets.
|
||||
|
||||
Under Profile Settings, leave `Default` selected.
|
||||
|
||||
Create an administrator account. The actual name is irrelevant, just don’t lost this login information.
|
||||
Create an administrator account. The actual name is irrelevant, just don’t lose this login information.
|
||||
|
||||
Finally, log in to the console with your admin account.
|
||||
|
||||
Edit your auth project's settings file and enter the values you just set:
|
||||
- `JABBER_URL` is the pubic address of your jabber server
|
||||
- `JABBER_PORT` is the port for clients to connect to (usually 5223)
|
||||
- `JABBER_SERVER` is the name of the jabber server. If you didn't alter it during install it'll usually be your domain (eg `example.com`)
|
||||
- `OPENFIRE_ADDRESS` is the web address of Openfire's web interface. Use http:// with port 9090 or https:// with port 9091 if you configure SSL in Openfire
|
||||
|
||||
### REST API Setup
|
||||
Navigate to the `plugins` tab, and then `Available Plugins` on the left navigation bar. You’ll need to fetch the list of available plugins by clicking the link.
|
||||
|
||||
@@ -60,17 +94,13 @@ Once loaded, press the green plus on the right for `REST API`.
|
||||
|
||||
Navigate the `Server` tab, `Sever Settings` subtab. At the bottom of the left navigation bar select `REST API`.
|
||||
|
||||
Select `Enabled`, and `Secret Key Auth`. Update Alliance Auth settings with this secret key as `OPENFIRE_SECRET_KEY`.
|
||||
Select `Enabled`, and `Secret Key Auth`. Update your auth project's settings with this secret key as `OPENFIRE_SECRET_KEY`.
|
||||
|
||||
### Broadcast Plugin Setup
|
||||
|
||||
Navigate to the `Users/Groups` tab and select `Create New User` from the left navigation bar.
|
||||
|
||||
Username is what you set in `BROADCAST_USER` without the @ sign, usually `broadcast`.
|
||||
|
||||
Password is what you set in `BROADCAST_USER_PASSWORD`
|
||||
|
||||
Press `Create User` to save this user.
|
||||
Pick a username (eg `broadcast`) and password for your ping user. Enter these in your auth project's settings file as `BROADCAST_USER` and `BROADCAST_USER_PASSWORD`. Note that `BROADCAST_USER` needs to be in the format `user@example.com` matching your jabber server name. Press `Create User` to save this user.
|
||||
|
||||
Broadcasting requires a plugin. Navigate to the `plugins` tab, press the green plus for the `Broadcast` plugin.
|
||||
|
||||
@@ -82,9 +112,13 @@ Navigate to the `Server` tab, `Server Manager` subtab, and select `System Proper
|
||||
- Name: `plugin.broadcast.allowedUsers`
|
||||
- Value: `broadcast@example.com`, replacing the domain name with yours
|
||||
- Do not encrypt this property value
|
||||
|
||||
|
||||
If you have troubles getting broadcasts to work, you can try setting the optional (you will need to add it) `BROADCAST_IGNORE_INVALID_CERT` setting to `True`. This will allow invalid certificates to be used when connecting to the Openfire server to send a broadcast.
|
||||
|
||||
### Preparing Auth
|
||||
|
||||
Once all settings are entered, run migrations and restart gunicorn and celery.
|
||||
|
||||
### Group Chat
|
||||
Channels are available which function like a chat room. Access can be controlled either by password or ACL (not unlike mumble).
|
||||
|
||||
@@ -98,6 +132,3 @@ Navigate to the `Group Chat` tab and select `Create New Room` from the left navi
|
||||
Now select your new room. On the left navigation bar, select `Permissions`.
|
||||
|
||||
ACL is achieved by assigning groups to each of the three tiers: `Owners`, `Admins` and `Members`. `Outcast` is the blacklist. You’ll usually only be assigning groups to the `Member` category.
|
||||
|
||||
## Setup Complete
|
||||
You’ve finished the steps required to make AllianceAuth work with Openfire. Play around with it and make it your own.
|
||||
|
||||
@@ -1,14 +1,41 @@
|
||||
# phpBB3
|
||||
|
||||
Add `allianceauth.services.modules.phpbb3` to your `INSTALLED_APPS` list and run migrations before continuing with this guide to ensure the service is installed.
|
||||
and run migrations before continuing with this guide to ensure the service is installed.
|
||||
|
||||
## Overview
|
||||
phpBB is a free php-based forum. It’s the default forum for AllianceAuth.
|
||||
|
||||
## Dependencies
|
||||
All dependencies should have been taken care of during setup.
|
||||
PHPBB3 requires php installed in your web server. Apache has `mod_php`, NGINX requires `php-fpm`. See [the official guide](https://www.phpbb.com/community/docs/INSTALL.html) for php package requirements.
|
||||
|
||||
## Prepare Your Settings
|
||||
In your auth project's settings file, do the following:
|
||||
- Add `'allianceauth.services.modules.phpbb3',` to your `INSTALLED_APPS` list
|
||||
- Append the following to the bottom of the settings file:
|
||||
|
||||
|
||||
# PHPBB3 Configuration
|
||||
PHPBB3_URL = ''
|
||||
DATABASES['phpbb3'] = {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'NAME': 'alliance_forum',
|
||||
'USER': 'allianceserver-phpbb3',
|
||||
'PASSWORD': 'password',
|
||||
'HOST': '127.0.0.1',
|
||||
'PORT': '3306',
|
||||
}
|
||||
|
||||
## Setup
|
||||
### Prepare the Database
|
||||
Create a database to install phpbb3 in.
|
||||
|
||||
mysql -u root -p
|
||||
create database alliance_forum;
|
||||
grant all privileges on alliance_forum . * to 'allianceserver'@'localhost';
|
||||
exit;
|
||||
|
||||
Edit your auth project's settings file and fill out the `DATABASES['phpbb3']` part.
|
||||
|
||||
### Download Phpbb3
|
||||
phpBB is available as a zip from their website. Navigate to the website’s [downloads section](https://www.phpbb.com/downloads/) using your PC browser and copy the URL for the latest version zip.
|
||||
|
||||
@@ -16,11 +43,11 @@ In the console, navigate to your user’s home directory: `cd ~`
|
||||
|
||||
Now download using wget, replacing the url with the url for the package you just retrieved
|
||||
|
||||
wget https://www.phpbb.com/files/release/phpBB-3.2.0.zip
|
||||
wget https://www.phpbb.com/files/release/phpBB-3.2.2.zip
|
||||
|
||||
This needs to be unpackaged. Unzip it, replacing the file name with that of the file you just downloaded
|
||||
|
||||
unzip phpBB-3.2.0.zip
|
||||
unzip phpBB-3.2.2.zip
|
||||
|
||||
Now we need to move this to our web directory. Usually `/var/www/forums`.
|
||||
|
||||
@@ -30,8 +57,51 @@ The web server needs read/write permission to this folder
|
||||
|
||||
sudo chown -R www-data:www-data /var/www/forums
|
||||
|
||||
### Configuring Web Server
|
||||
You will need to configure you web server to serve PHPBB3 before proceeding with installation.
|
||||
|
||||
A minimal apache config file might look like:
|
||||
|
||||
<VirtualHost *:80>
|
||||
ServerName forums.example.com
|
||||
DocumentRoot /var/www/forums
|
||||
<Directory /var/www/forums>
|
||||
Require all granted
|
||||
DirectoryIndex index.php
|
||||
</Directory>
|
||||
</VirtualHost>
|
||||
|
||||
A minimal nginx config file might look like:
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name forums.example.com;
|
||||
root /var/www/forums;
|
||||
index index.php;
|
||||
access_log /var/logs/forums.access.log;
|
||||
|
||||
location ~ /(config\.php|common\.php|cache|files|images/avatars/upload|includes|store) {
|
||||
deny all;
|
||||
return 403;
|
||||
}
|
||||
|
||||
location ~* \.(gif|jpe?g|png|css)$ {
|
||||
expires 30d;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
try_files $uri =404;
|
||||
fastcgi_pass unix:/tmp/php.socket;
|
||||
fastcgi_index index.php;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
include fastcgi_params;
|
||||
}
|
||||
}
|
||||
|
||||
Enter your forum's web address as the `PHPBB3_URL` setting in your auth project's settings file.
|
||||
|
||||
### Web Install
|
||||
Navigate to http://example.com/forums where you will be presented with an installer.
|
||||
Navigate to your forums web address where you will be presented with an installer.
|
||||
|
||||
Click on the `Install` tab.
|
||||
|
||||
@@ -42,9 +112,11 @@ Under Database Settings, set the following:
|
||||
- Database Server Hostname is `127.0.0.1`
|
||||
- Database Server Port is left blank
|
||||
- Database Name is `alliance_forum`
|
||||
- Database Username is your MySQL user for AllianceAuth, usually `allianceserver`
|
||||
- Database Username is your auth MySQL user, usually `allianceserver`
|
||||
- Database Password is this user’s password
|
||||
|
||||
If you use a table prefix other than the standard `phpbb_` you need to add an additional setting to your auth project's settings file, `PHPBB3_TABLE_PREFIX = ''`, and enter the prefix.
|
||||
|
||||
You should see `Succesful Connection` and proceed.
|
||||
|
||||
Enter administrator credentials on the next page.
|
||||
@@ -67,5 +139,5 @@ You can allow members to overwrite the portrait with a custom image if desired.
|
||||
|
||||

|
||||
|
||||
## Setup Complete
|
||||
You’ve finished the steps required to make AllianceAuth work with phpBB. Play around with it and make it your own.
|
||||
### Prepare Auth
|
||||
Once settings have been configured, run migrations and restart gunicorn and celery.
|
||||
|
||||
@@ -1,20 +1,33 @@
|
||||
# SMF
|
||||
|
||||
Add `allianceauth.services.modules.smf` to your `INSTALLED_APPS` list and run migrations before continuing with this guide to ensure the service is installed.
|
||||
|
||||
## Overview
|
||||
SMF is a free php-based forum. It’s the one of the forums for AllianceAuth.
|
||||
SMF is a free php-based forum.
|
||||
|
||||
## Dependencies
|
||||
All dependencies should have been taken care of during setup.
|
||||
SMF requires php installed in your web server. Apache has `mod_php`, NGINX requires `php-fpm`. More details can be found in the [SMF requirements page.](https://download.simplemachines.org/requirements.php)
|
||||
|
||||
## Prepare Your Settings
|
||||
In your auth project's settings file, do the following:
|
||||
- Add `'allianceauth.services.modules.smf',` to your `INSTALLED_APPS` list
|
||||
- Append the following to the bottom of the settings file:
|
||||
|
||||
|
||||
# SMF Configuration
|
||||
SMF_URL = ''
|
||||
DATABASES['smf'] = {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'NAME': 'alliance_smf',
|
||||
'USER': 'allianceserver-smf',
|
||||
'PASSWORD': 'password',
|
||||
'HOST': '127.0.0.1',
|
||||
'PORT': '3306',
|
||||
}
|
||||
|
||||
## Setup
|
||||
### Download SMF
|
||||
Using your browser, you can download the latest version of SMF to your desktop computer. All SMF downloads can be found at SMF Downloads. The latest recommended version will always be available at http://www.simplemachines.org/download/index.php/latest/install/.
|
||||
|
||||
In the console, navigate to your user’s home directory: `cd ~`
|
||||
|
||||
Now download using wget, replacing the url with the url for the package you just retrieved
|
||||
Download using wget, replacing the url with the url for the package you just retrieved
|
||||
|
||||
wget http://download.simplemachines.org/index.php?thanks;filename=smf_2-0-13_install.zip
|
||||
|
||||
@@ -30,8 +43,54 @@ The web server needs read/write permission to this folder
|
||||
|
||||
sudo chown -R www-data:www-data /var/www/forums
|
||||
|
||||
### Database Preparation
|
||||
SMF needs a database. Create one:
|
||||
|
||||
mysql -u root -p
|
||||
create database alliance_smf;
|
||||
grant all privileges on alliance_smf . * to 'allianceserver'@'localhost';
|
||||
exit;
|
||||
|
||||
Enter the database information into the `DATABASES['smf']` section of your auth project's settings file.
|
||||
|
||||
### Web Server Configuration
|
||||
Your web server needs to be configured to serve Alliance Market.
|
||||
|
||||
A minimal apache config might look like:
|
||||
|
||||
<VirtualHost *:80>
|
||||
ServerName forums.example.com
|
||||
DocumentRoot /var/www/forums
|
||||
<Directory "/var/www/forums">
|
||||
DirectoryIndex index.php
|
||||
</Directory>
|
||||
</VirtualHost>
|
||||
|
||||
A minimal nginx config might look like:
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name forums.example.com;
|
||||
root /var/www/forums;
|
||||
index app.php;
|
||||
access_log /var/logs/forums.access.log;
|
||||
|
||||
location ~ \.php$ {
|
||||
try_files $uri =404;
|
||||
fastcgi_pass unix:/tmp/php.socket;
|
||||
fastcgi_index index.php;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
include fastcgi_params;
|
||||
}
|
||||
}
|
||||
|
||||
Enter the web address to your forums into the `SMF_URL` setting in your auth project's settings file.
|
||||
|
||||
### Preparing Auth
|
||||
Once settings are entered, apply migrations and restart gunicorn and celery.
|
||||
|
||||
### Web Install
|
||||
Navigate to http://example.com/forums where you will be presented with an installer.
|
||||
Navigate to your forums address where you will be presented with an installer.
|
||||
|
||||
Click on the `Install` tab.
|
||||
|
||||
@@ -42,11 +101,9 @@ Under Database Settings, set the following:
|
||||
- Database Server Hostname is `127.0.0.1`
|
||||
- Database Server Port is left blank
|
||||
- Database Name is `alliance_smf`
|
||||
- Database Username is your MySQL user for AllianceAuth, usually `allianceserver`
|
||||
- Database Username is your auth MySQL user, usually `allianceserver`
|
||||
- Database Password is this user’s password
|
||||
|
||||
Follow the Directions in the installer.
|
||||
If you use a table prefix other than the standard `smf_` you need to add an additional setting to your auth project's settings file, `SMF_TABLE_PREFIX = ''`, and enter the prefix.
|
||||
|
||||
|
||||
## Setup Complete
|
||||
You’ve finished the steps required to make AllianceAuth work with SMF. Play around with it and make it your own.
|
||||
Follow the directions in the installer.
|
||||
@@ -1,30 +1,42 @@
|
||||
# Teamspeak 3
|
||||
|
||||
Add `allianceauth.services.modules.teamspeak3` to your `INSTALLED_APPS` list and run migrations before continuing with this guide to ensure the service is installed.
|
||||
|
||||
## Overview
|
||||
Teamspeak3 is the most popular VOIP program for gamers.
|
||||
|
||||
But have you considered using Mumble? Not only is it free, but it has features and performance far superior to Teamspeak3.
|
||||
|
||||
## Dependencies
|
||||
All dependencies should have been taken care of during the AllianceAuth install.
|
||||
|
||||
## Setup
|
||||
Sticking with it? Alright, I tried.
|
||||
Sticking with TS3? Alright, I tried.
|
||||
|
||||
### Prepare Your Settings
|
||||
In your auth project's settings file, do the following:
|
||||
- Add `'allianceauth.services.modules.teamspeak3',` to your `INSTALLED_APPS` list
|
||||
- Append the following to the bottom of the settings file:
|
||||
|
||||
|
||||
# Teamspeak3 Configuration
|
||||
TEAMSPEAK3_SERVER_IP = '127.0.0.1'
|
||||
TEAMSPEAK3_SERVER_PORT = 10011
|
||||
TEAMSPEAK3_SERVERQUERY_USER = 'serveradmin'
|
||||
TEAMSPEAK3_SERVERQUERY_PASSWORD = ''
|
||||
TEAMSPEAK3_VIRTUAL_SERVER = 1
|
||||
TEAMSPEAK3_PUBLIC_URL = ''
|
||||
|
||||
CELERYBEAT_SCHEDULE['run_ts3_group_update'] = {
|
||||
'task': 'services.modules.teamspeak3.tasks.run_ts3_group_update',
|
||||
'schedule': crontab(minute='*/30'),
|
||||
}
|
||||
|
||||
### Download Installer
|
||||
To install we need a copy of the server. You can find the latest version from [this dl server](http://dl.4players.de/ts/releases/) (I’d recommed getting the latest stable version – find this version number from the [TeamSpeak site](https://www.teamspeak.com/downloads#)). Be sure to get a link to the linux version.
|
||||
|
||||
From the console, ensure you’re in the user’s home directory: `cd ~`
|
||||
Download the server, replacing the link with the link you got earlier.
|
||||
|
||||
And now download the server, replacing the link with the link you got earlier.
|
||||
|
||||
http://dl.4players.de/ts/releases/3.0.13.6/teamspeak3-server_linux_amd64-3.0.13.6.tar.bz2
|
||||
http://dl.4players.de/ts/releases/3.1.0/teamspeak3-server_linux_amd64-3.1.0.tar.bz2
|
||||
|
||||
Now we need to extract the file.
|
||||
|
||||
tar -xf teamspeak3-server_linux_amd64-3.0.13.6.tar.bz2
|
||||
tar -xf teamspeak3-server_linux_amd64-3.1.0.tar.bz2
|
||||
|
||||
### Create User
|
||||
Teamspeak needs its own user.
|
||||
@@ -48,18 +60,18 @@ Finally we start the server.
|
||||
sudo service teamspeak start
|
||||
|
||||
### Update Settings
|
||||
The console will spit out a block of text. **SAVE THIS**.
|
||||
|
||||
Update the AllianceAuth settings file with the following from that block of text:
|
||||
- `TEAMSPEAK3_SERVERQUERY_USER` is `loginname` (usually `serveradmin`)
|
||||
- `TEAMSPEAK3_SERVERQUERY_PASSWORD` is `password`
|
||||
|
||||
Save and reload apache. Restart celery workers as well.
|
||||
|
||||
sudo service apache2 reload
|
||||
The console will spit out a block of text. If it does not appear, it can be found with `sudo service teamspeak status`. **SAVE THIS**.
|
||||
|
||||
If you plan on claiming the ServerAdmin token, do so with a different TeamSpeak client profile than the one used for your auth account, or you will lose your admin status.
|
||||
|
||||
Edit the settings you added to your auth project's settings file earlier, entering the following:
|
||||
- `TEAMSPEAK3_SERVERQUERY_USER` is `loginname` from that block of text it just spat out (usually `serveradmin`)
|
||||
- `TEAMSPEAK3_SERVERQUERY_PASSWORD` is `password` from that block of text it just spat out
|
||||
- `TEAMSPEAK_VIRTUAL_SERVER` is the virtual server ID of the server to be managed - it will only ever not be 1 if your server is hosted by a professional company
|
||||
- `TEAMSPEAK3_PUBLIC_URL` is the public address of your teamspeak server. Do not include any leading http:// or teamspeak://
|
||||
|
||||
Once settings are entered, run migrations and restart gunicorn and celery.
|
||||
|
||||
### Generate User Account
|
||||
And now we can generate ourselves a user account. Navigate to the services in AllianceAuth for your user account and press the checkmark for TeamSpeak 3.
|
||||
|
||||
@@ -82,13 +94,14 @@ Using the advanced permissions editor, ensure the `Guest` group has the permissi
|
||||
To enable advanced permissions, on your client go to the `Tools` menu, `Application`, and under the `Misc` section, tick `Advanced permission system`
|
||||
|
||||
### TS group models not populating on admin site
|
||||
The method which populates these runs every 30 minutes. To populate manually, start a celery shell:
|
||||
The method which populates these runs every 30 minutes. To populate manually, start a django shell:
|
||||
|
||||
celery -A alliance_auth shell
|
||||
python manage.py shell
|
||||
|
||||
And execute the update:
|
||||
|
||||
run_ts3_group_update()
|
||||
from services.modules.teamspeak3.tasks import Teamspeak3Tasks
|
||||
Teamspeak3Tasks.run_ts3_group_update()
|
||||
|
||||
Ensure that command does not return an error.
|
||||
|
||||
@@ -110,8 +123,8 @@ If you have SSH access to the server hosting it, you need to locate the teamspea
|
||||
|
||||
### `520 invalid loginname or password`
|
||||
|
||||
The serverquery account login specified in settings.py is incorrect. Please verify `TEAMSPEAK3_SERVERQUERY_USER` and `TEAMSPEAK3_SERVERQUERY_PASSWORD`. The [installation section](#update-settings) describes where to get them.
|
||||
The serverquery account login specified in local.py is incorrect. Please verify `TEAMSPEAK3_SERVERQUERY_USER` and `TEAMSPEAK3_SERVERQUERY_PASSWORD`. The [installation section](#update-settings) describes where to get them.
|
||||
|
||||
### `2568 insufficient client permissions`
|
||||
|
||||
This usually occurs if you've created a separate serverquery user to use with auth. It has not been assigned sufficient permissions to complete all the tasks required of it. The full list of required permissions is not known, so assign liberally.
|
||||
This usually occurs if you've created a separate serverquery user to use with auth. It has not been assigned sufficient permissions to complete all the tasks required of it. The full list of required permissions is not known, so assign liberally.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user