mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2026-02-04 14:16:21 +01:00
Compare commits
123 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
507eda8a7d | ||
|
|
cbe67e9ebc | ||
|
|
cd38200506 | ||
|
|
5d5cf92a19 | ||
|
|
98230d0ee3 | ||
|
|
e47c04a0b0 | ||
|
|
b65ccac58f | ||
|
|
bee69cc250 | ||
|
|
a350e175c7 | ||
|
|
2cd8188ffb | ||
|
|
b8a2d65a1d | ||
|
|
95f72c854d | ||
|
|
cd8bcfbbb5 | ||
|
|
08f89d2844 | ||
|
|
f3f156bf57 | ||
|
|
73e6f576f4 | ||
|
|
20236cab8a | ||
|
|
6c7b65edad | ||
|
|
21782293ea | ||
|
|
e52478c9aa | ||
|
|
319cba8653 | ||
|
|
df3acccc50 | ||
|
|
19282cac60 | ||
|
|
933c12b48d | ||
|
|
8a73890646 | ||
|
|
d6df5184a6 | ||
|
|
91e1a374b4 | ||
|
|
c725de7b5b | ||
|
|
ad1fd633b1 | ||
|
|
ef9284030b | ||
|
|
89e5740027 | ||
|
|
106f6bbcea | ||
|
|
b53c7a624b | ||
|
|
6fa788a8f9 | ||
|
|
19f0788f47 | ||
|
|
7767226000 | ||
|
|
4eb6b73903 | ||
|
|
cb46ecb002 | ||
|
|
e694921fe6 | ||
|
|
8266661855 | ||
|
|
cf7ddbe0e1 | ||
|
|
bdb3ab366f | ||
|
|
1fc71a0738 | ||
|
|
ad79b4f77c | ||
|
|
fd876b8636 | ||
|
|
21e896642a | ||
|
|
b4c395f116 | ||
|
|
a38116014d | ||
|
|
54223db1e9 | ||
|
|
8a897abc7b | ||
|
|
fe7b078ec8 | ||
|
|
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 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,6 +8,7 @@ __pycache__/
|
|||||||
# Distribution / packaging
|
# Distribution / packaging
|
||||||
.Python
|
.Python
|
||||||
env/
|
env/
|
||||||
|
venv/
|
||||||
build/
|
build/
|
||||||
develop-eggs/
|
develop-eggs/
|
||||||
dist/
|
dist/
|
||||||
|
|||||||
28
README.md
28
README.md
@@ -7,8 +7,7 @@ Alliance Auth
|
|||||||
[](https://coveralls.io/github/allianceauth/allianceauth?branch=master)
|
[](https://coveralls.io/github/allianceauth/allianceauth?branch=master)
|
||||||
|
|
||||||
|
|
||||||
EVE service auth to help corps, alliances, and coalitions manage services.
|
An auth system for EVE Online to help in-game organizations manage online service access.
|
||||||
Built for "The 99 Percent" open for anyone to use.
|
|
||||||
|
|
||||||
[Read the docs here.](http://allianceauth.rtfd.io)
|
[Read the docs here.](http://allianceauth.rtfd.io)
|
||||||
|
|
||||||
@@ -17,22 +16,19 @@ Built for "The 99 Percent" open for anyone to use.
|
|||||||
|
|
||||||
Active Developers:
|
Active Developers:
|
||||||
|
|
||||||
- [Adarnof](https://github.com/Adarnof)
|
- [Adarnof](https://github.com/adarnof/)
|
||||||
- [Basraah](https://github.com/basraah)
|
- [Basraah](https://github.com/basraah/)
|
||||||
|
|
||||||
|
|
||||||
Beta Testers / Bug Fixers:
|
Beta Testers / Bug Fixers:
|
||||||
|
|
||||||
- [ghoti](https://github.com/ghoti)
|
- [ghoti](https://github.com/ghoti/)
|
||||||
- [mmolitor87](https://github.com/mmolitor87)
|
- [mmolitor87](https://github.com/mmolitor87/)
|
||||||
|
- [TargetZ3R0](https://github.com/TargetZ3R0)
|
||||||
|
- [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:
|
### 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.
|
||||||
- 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.
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# This will make sure the app is always imported when
|
# This will make sure the app is always imported when
|
||||||
# Django starts so that shared_task will use this app.
|
# Django starts so that shared_task will use this app.
|
||||||
|
|
||||||
__version__ = '2.0b2'
|
__version__ = '2.0.0'
|
||||||
NAME = 'Alliance Auth v%s' % __version__
|
NAME = 'Alliance Auth v%s' % __version__
|
||||||
default_app_config = 'allianceauth.apps.AllianceAuthConfig'
|
default_app_config = 'allianceauth.apps.AllianceAuthConfig'
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
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.utils.text import slugify
|
||||||
|
from django.db.models import Q
|
||||||
from allianceauth.services.hooks import ServicesHook
|
from allianceauth.services.hooks import ServicesHook
|
||||||
|
from django.db.models.signals import pre_save, post_save, pre_delete, post_delete, m2m_changed
|
||||||
from allianceauth.authentication.models import State, get_guest_state, CharacterOwnership, UserProfile
|
from django.dispatch import receiver
|
||||||
|
from allianceauth.authentication.models import State, get_guest_state, CharacterOwnership, UserProfile, OwnershipRecord
|
||||||
from allianceauth.hooks import get_hooks
|
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):
|
def make_service_hooks_update_groups_action(service):
|
||||||
@@ -38,6 +42,47 @@ def make_service_hooks_sync_nickname_action(service):
|
|||||||
return sync_nickname
|
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):
|
class UserAdmin(BaseUserAdmin):
|
||||||
"""
|
"""
|
||||||
Extending Django's UserAdmin model
|
Extending Django's UserAdmin model
|
||||||
@@ -62,6 +107,25 @@ class UserAdmin(BaseUserAdmin):
|
|||||||
|
|
||||||
return actions
|
return actions
|
||||||
list_filter = BaseUserAdmin.list_filter + ('profile__state',)
|
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)
|
@admin.register(State)
|
||||||
@@ -96,31 +160,30 @@ class StateAdmin(admin.ModelAdmin):
|
|||||||
return obj.userprofile_set.all().count()
|
return obj.userprofile_set.all().count()
|
||||||
|
|
||||||
|
|
||||||
@admin.register(UserProfile)
|
class BaseOwnershipAdmin(admin.ModelAdmin):
|
||||||
class UserProfileAdmin(admin.ModelAdmin):
|
list_display = ('user', 'character')
|
||||||
readonly_fields = ('user', 'state')
|
search_fields = ('user__username', 'character__character_name', 'character__corporation_name', 'character__alliance_name')
|
||||||
search_fields = ('user__username', 'main_character__character_name')
|
|
||||||
list_filter = ('state',)
|
|
||||||
list_display = ('user', 'main_character')
|
|
||||||
actions = None
|
|
||||||
|
|
||||||
def has_add_permission(self, request):
|
def get_readonly_fields(self, request, obj=None):
|
||||||
return False
|
if obj and obj.pk:
|
||||||
|
return 'owner_hash', 'character'
|
||||||
|
return tuple()
|
||||||
|
|
||||||
def has_delete_permission(self, request, obj=None):
|
|
||||||
return False
|
@admin.register(OwnershipRecord)
|
||||||
|
class OwnershipRecordAdmin(BaseOwnershipAdmin):
|
||||||
|
list_display = BaseOwnershipAdmin.list_display + ('created',)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(CharacterOwnership)
|
@admin.register(CharacterOwnership)
|
||||||
class CharacterOwnershipAdmin(admin.ModelAdmin):
|
class CharacterOwnershipAdmin(BaseOwnershipAdmin):
|
||||||
list_display = ('user', 'character')
|
def has_add_permission(self, request):
|
||||||
search_fields = ('user__username', 'character__character_name', 'character__corporation_name', 'character__alliance_name')
|
return False
|
||||||
readonly_fields = ('owner_hash', 'character')
|
|
||||||
|
|
||||||
|
|
||||||
class PermissionAdmin(admin.ModelAdmin):
|
class PermissionAdmin(admin.ModelAdmin):
|
||||||
actions = None
|
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_display = ('admin_name', 'name', 'codename', 'content_type')
|
||||||
list_filter = ('content_type__app_label',)
|
list_filter = ('content_type__app_label',)
|
||||||
|
|
||||||
@@ -134,23 +197,61 @@ class PermissionAdmin(admin.ModelAdmin):
|
|||||||
def has_delete_permission(self, request, obj=None):
|
def has_delete_permission(self, request, obj=None):
|
||||||
return False
|
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
|
# Hack to allow registration of django.contrib.auth models in our authentication app
|
||||||
class ProxyUser(User):
|
class User(BaseUser):
|
||||||
class Meta:
|
class Meta:
|
||||||
proxy = True
|
proxy = True
|
||||||
verbose_name = User._meta.verbose_name
|
verbose_name = BaseUser._meta.verbose_name
|
||||||
verbose_name_plural = User._meta.verbose_name_plural
|
verbose_name_plural = BaseUser._meta.verbose_name_plural
|
||||||
|
|
||||||
|
|
||||||
class ProxyPermission(Permission):
|
class Permission(BasePermission):
|
||||||
class Meta:
|
class Meta:
|
||||||
proxy = True
|
proxy = True
|
||||||
verbose_name = Permission._meta.verbose_name
|
verbose_name = BasePermission._meta.verbose_name
|
||||||
verbose_name_plural = Permission._meta.verbose_name_plural
|
verbose_name_plural = BasePermission._meta.verbose_name_plural
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
admin.site.unregister(User)
|
admin.site.unregister(BaseUser)
|
||||||
finally:
|
finally:
|
||||||
admin.site.register(ProxyUser, UserAdmin)
|
admin.site.register(User, UserAdmin)
|
||||||
admin.site.register(ProxyPermission, PermissionAdmin)
|
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)
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
from django.contrib.auth.backends import ModelBackend
|
from django.contrib.auth.backends import ModelBackend
|
||||||
from django.contrib.auth.models import Permission
|
from django.contrib.auth.models import Permission
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
import logging
|
||||||
|
from .models import UserProfile, CharacterOwnership, OwnershipRecord
|
||||||
|
|
||||||
from .models import UserProfile, CharacterOwnership
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class StateBackend(ModelBackend):
|
class StateBackend(ModelBackend):
|
||||||
@@ -30,32 +33,48 @@ class StateBackend(ModelBackend):
|
|||||||
try:
|
try:
|
||||||
ownership = CharacterOwnership.objects.get(character__character_id=token.character_id)
|
ownership = CharacterOwnership.objects.get(character__character_id=token.character_id)
|
||||||
if ownership.owner_hash == token.character_owner_hash:
|
if ownership.owner_hash == token.character_owner_hash:
|
||||||
|
logger.debug('Authenticating {0} by ownership of character {1}'.format(ownership.user, token.character_name))
|
||||||
return ownership.user
|
return ownership.user
|
||||||
else:
|
else:
|
||||||
|
logger.debug('{0} has changed ownership. Creating new user account.'.format(token.character_name))
|
||||||
ownership.delete()
|
ownership.delete()
|
||||||
return self.create_user(token)
|
return self.create_user(token)
|
||||||
except CharacterOwnership.DoesNotExist:
|
except CharacterOwnership.DoesNotExist:
|
||||||
try:
|
try:
|
||||||
# insecure legacy main check for pre-sso registration auth installs
|
# insecure legacy main check for pre-sso registration auth installs
|
||||||
profile = UserProfile.objects.get(main_character__character_id=token.character_id)
|
profile = UserProfile.objects.get(main_character__character_id=token.character_id)
|
||||||
|
logger.debug('Authenticating {0} by their main character {1} without active ownership.'.format(profile.user, profile.main_character))
|
||||||
# attach an ownership
|
# attach an ownership
|
||||||
token.user = profile.user
|
token.user = profile.user
|
||||||
CharacterOwnership.objects.create_by_token(token)
|
CharacterOwnership.objects.create_by_token(token)
|
||||||
return profile.user
|
return profile.user
|
||||||
except UserProfile.DoesNotExist:
|
except UserProfile.DoesNotExist:
|
||||||
pass
|
# now we check historical records to see if this is a returning user
|
||||||
|
records = OwnershipRecord.objects.filter(owner_hash=token.character_owner_hash).filter(character__character_id=token.character_id)
|
||||||
|
if records.exists():
|
||||||
|
# we've seen this character owner before. Re-attach to their old user account
|
||||||
|
user = records[0].user
|
||||||
|
token.user = user
|
||||||
|
co = CharacterOwnership.objects.create_by_token(token)
|
||||||
|
logger.debug('Authenticating {0} by matching owner hash record of character {1}'.format(user, co.character))
|
||||||
|
if not user.profile.main_character:
|
||||||
|
# set this as their main by default if they have none
|
||||||
|
user.profile.main_character = co.character
|
||||||
|
user.profile.save()
|
||||||
|
return user
|
||||||
|
logger.debug('Unable to authenticate character {0}. Creating new user.'.format(token.character_name))
|
||||||
return self.create_user(token)
|
return self.create_user(token)
|
||||||
|
|
||||||
def create_user(self, token):
|
def create_user(self, token):
|
||||||
username = self.iterate_username(token.character_name) # build unique username off character name
|
username = self.iterate_username(token.character_name) # build unique username off character name
|
||||||
user = User.objects.create_user(username)
|
user = User.objects.create_user(username, is_active=False) # prevent login until email set
|
||||||
user.set_unusable_password() # prevent login via password
|
user.set_unusable_password() # prevent login via password
|
||||||
user.is_active = False # prevent login until email set
|
|
||||||
user.save()
|
user.save()
|
||||||
token.user = user
|
token.user = user
|
||||||
co = CharacterOwnership.objects.create_by_token(token) # assign ownership to this user
|
co = CharacterOwnership.objects.create_by_token(token) # assign ownership to this user
|
||||||
user.profile.main_character = co.character # assign main character as token character
|
user.profile.main_character = co.character # assign main character as token character
|
||||||
user.profile.save()
|
user.profile.save()
|
||||||
|
logger.debug('Created new user {0}'.format(user))
|
||||||
return user
|
return user
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
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)
|
||||||
0
allianceauth/authentication/management/__init__.py
Normal file
0
allianceauth/authentication/management/__init__.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from allianceauth.authentication.models import UserProfile
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Ensures all main characters have an active ownership'
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
profiles = UserProfile.objects.filter(main_character__isnull=False).filter(
|
||||||
|
main_character__character_ownership__isnull=True)
|
||||||
|
if profiles.exists():
|
||||||
|
for profile in profiles:
|
||||||
|
self.stdout.write(self.style.ERROR(
|
||||||
|
'{0} does not have an ownership. Resetting user {1} main character.'.format(profile.main_character,
|
||||||
|
profile.user)))
|
||||||
|
profile.main_character = None
|
||||||
|
profile.save()
|
||||||
|
self.stdout.write(self.style.WARNING('Reset {0} main characters.'.format(profiles.count())))
|
||||||
|
else:
|
||||||
|
self.stdout.write(self.style.SUCCESS('All main characters have active ownership.'))
|
||||||
@@ -96,6 +96,11 @@ def create_blue_group(apps, schema_editor):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def purge_tokens(apps, schema_editor):
|
||||||
|
Token = apps.get_model('esi', 'Token')
|
||||||
|
Token.objects.filter(refresh_token__isnull=True).delete()
|
||||||
|
|
||||||
|
|
||||||
def populate_ownerships(apps, schema_editor):
|
def populate_ownerships(apps, schema_editor):
|
||||||
Token = apps.get_model('esi', 'Token')
|
Token = apps.get_model('esi', 'Token')
|
||||||
CharacterOwnership = apps.get_model('authentication', 'CharacterOwnership')
|
CharacterOwnership = apps.get_model('authentication', 'CharacterOwnership')
|
||||||
@@ -145,7 +150,7 @@ def recreate_authservicesinfo(apps, schema_editor):
|
|||||||
User = apps.get_model('auth', 'User')
|
User = apps.get_model('auth', 'User')
|
||||||
|
|
||||||
# recreate all missing AuthServicesInfo models
|
# 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
|
# repopulate main characters
|
||||||
for profile in UserProfile.objects.exclude(main_character__isnull=True).select_related('user', 'main_character'):
|
for profile in UserProfile.objects.exclude(main_character__isnull=True).select_related('user', 'main_character'):
|
||||||
@@ -203,7 +208,6 @@ class Migration(migrations.Migration):
|
|||||||
('permissions', models.ManyToManyField(blank=True, to='auth.Permission')),
|
('permissions', models.ManyToManyField(blank=True, to='auth.Permission')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'default_permissions': ('change',),
|
|
||||||
'ordering': ['-priority'],
|
'ordering': ['-priority'],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -222,6 +226,7 @@ class Migration(migrations.Migration):
|
|||||||
migrations.RunPython(create_guest_state, migrations.RunPython.noop),
|
migrations.RunPython(create_guest_state, migrations.RunPython.noop),
|
||||||
migrations.RunPython(create_member_state, create_member_group),
|
migrations.RunPython(create_member_state, create_member_group),
|
||||||
migrations.RunPython(create_blue_state, create_blue_group),
|
migrations.RunPython(create_blue_state, create_blue_group),
|
||||||
|
migrations.RunPython(purge_tokens, migrations.RunPython.noop),
|
||||||
migrations.RunPython(populate_ownerships, migrations.RunPython.noop),
|
migrations.RunPython(populate_ownerships, migrations.RunPython.noop),
|
||||||
migrations.RunPython(create_profiles, recreate_authservicesinfo),
|
migrations.RunPython(create_profiles, recreate_authservicesinfo),
|
||||||
migrations.RemoveField(
|
migrations.RemoveField(
|
||||||
@@ -233,7 +238,7 @@ class Migration(migrations.Migration):
|
|||||||
),
|
),
|
||||||
migrations.RunPython(disable_passwords, migrations.RunPython.noop),
|
migrations.RunPython(disable_passwords, migrations.RunPython.noop),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='ProxyPermission',
|
name='Permission',
|
||||||
fields=[
|
fields=[
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
@@ -247,7 +252,7 @@ class Migration(migrations.Migration):
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='ProxyUser',
|
name='User',
|
||||||
fields=[
|
fields=[
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# Generated by Django 2.0.4 on 2018-04-14 18:28
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
def create_initial_records(apps, schema_editor):
|
||||||
|
OwnershipRecord = apps.get_model('authentication', 'OwnershipRecord')
|
||||||
|
CharacterOwnership = apps.get_model('authentication', 'CharacterOwnership')
|
||||||
|
|
||||||
|
OwnershipRecord.objects.bulk_create([
|
||||||
|
OwnershipRecord(user=o.user, character=o.character, owner_hash=o.owner_hash) for o in CharacterOwnership.objects.all()
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('eveonline', '0009_on_delete'),
|
||||||
|
('authentication', '0015_user_profiles'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='OwnershipRecord',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('owner_hash', models.CharField(db_index=True, max_length=28)),
|
||||||
|
('created', models.DateTimeField(auto_now=True)),
|
||||||
|
('character', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ownership_records', to='eveonline.EveCharacter')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ownership_records', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ['-created'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.RunPython(create_initial_records, migrations.RunPython.noop)
|
||||||
|
]
|
||||||
@@ -96,3 +96,16 @@ class CharacterOwnership(models.Model):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s: %s" % (self.user, self.character)
|
return "%s: %s" % (self.user, self.character)
|
||||||
|
|
||||||
|
|
||||||
|
class OwnershipRecord(models.Model):
|
||||||
|
character = models.ForeignKey(EveCharacter, on_delete=models.CASCADE, related_name='ownership_records')
|
||||||
|
owner_hash = models.CharField(max_length=28, db_index=True)
|
||||||
|
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='ownership_records')
|
||||||
|
created = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['-created']
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "%s: %s on %s" % (self.user, self.character, self.created)
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .models import CharacterOwnership, UserProfile, get_guest_state, State
|
from .models import CharacterOwnership, UserProfile, get_guest_state, State, OwnershipRecord
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.db.models import Q
|
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, post_delete, m2m_changed
|
||||||
from django.dispatch import receiver, Signal
|
from django.dispatch import receiver, Signal
|
||||||
from esi.models import Token
|
from esi.models import Token
|
||||||
|
|
||||||
@@ -11,7 +11,6 @@ from allianceauth.eveonline.models import EveCharacter
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
state_changed = Signal(providing_args=['user', 'state'])
|
state_changed = Signal(providing_args=['user', 'state'])
|
||||||
|
|
||||||
|
|
||||||
@@ -32,23 +31,27 @@ def trigger_state_check(state):
|
|||||||
@receiver(m2m_changed, sender=State.member_characters.through)
|
@receiver(m2m_changed, sender=State.member_characters.through)
|
||||||
def state_member_characters_changed(sender, instance, action, *args, **kwargs):
|
def state_member_characters_changed(sender, instance, action, *args, **kwargs):
|
||||||
if action.startswith('post_'):
|
if action.startswith('post_'):
|
||||||
|
logger.debug('State {} member characters changed. Re-evaluating membership.'.format(instance))
|
||||||
trigger_state_check(instance)
|
trigger_state_check(instance)
|
||||||
|
|
||||||
|
|
||||||
@receiver(m2m_changed, sender=State.member_corporations.through)
|
@receiver(m2m_changed, sender=State.member_corporations.through)
|
||||||
def state_member_corporations_changed(sender, instance, action, *args, **kwargs):
|
def state_member_corporations_changed(sender, instance, action, *args, **kwargs):
|
||||||
if action.startswith('post_'):
|
if action.startswith('post_'):
|
||||||
|
logger.debug('State {} member corporations changed. Re-evaluating membership.'.format(instance))
|
||||||
trigger_state_check(instance)
|
trigger_state_check(instance)
|
||||||
|
|
||||||
|
|
||||||
@receiver(m2m_changed, sender=State.member_alliances.through)
|
@receiver(m2m_changed, sender=State.member_alliances.through)
|
||||||
def state_member_alliances_changed(sender, instance, action, *args, **kwargs):
|
def state_member_alliances_changed(sender, instance, action, *args, **kwargs):
|
||||||
if action.startswith('post_'):
|
if action.startswith('post_'):
|
||||||
|
logger.debug('State {} member alliances changed. Re-evaluating membership.'.format(instance))
|
||||||
trigger_state_check(instance)
|
trigger_state_check(instance)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=State)
|
@receiver(post_save, sender=State)
|
||||||
def state_saved(sender, instance, *args, **kwargs):
|
def state_saved(sender, instance, *args, **kwargs):
|
||||||
|
logger.debug('State {} saved. Re-evaluating membership.'.format(instance))
|
||||||
trigger_state_check(instance)
|
trigger_state_check(instance)
|
||||||
|
|
||||||
|
|
||||||
@@ -59,6 +62,7 @@ def reassess_on_profile_save(sender, instance, created, *args, **kwargs):
|
|||||||
if not created:
|
if not created:
|
||||||
update_fields = kwargs.pop('update_fields', []) or []
|
update_fields = kwargs.pop('update_fields', []) or []
|
||||||
if 'state' not in update_fields:
|
if 'state' not in update_fields:
|
||||||
|
logger.debug('Profile for {} saved without state change. Re-evaluating state.'.format(instance.user))
|
||||||
instance.assign_state()
|
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):
|
def create_required_models(sender, instance, created, *args, **kwargs):
|
||||||
# ensure all users have a model
|
# ensure all users have a model
|
||||||
if created:
|
if created:
|
||||||
|
logger.debug('User {} created. Creating default UserProfile.'.format(instance))
|
||||||
UserProfile.objects.get_or_create(user=instance)
|
UserProfile.objects.get_or_create(user=instance)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Token)
|
@receiver(post_save, sender=Token)
|
||||||
def record_character_ownership(sender, instance, created, *args, **kwargs):
|
def record_character_ownership(sender, instance, created, *args, **kwargs):
|
||||||
if created:
|
if created:
|
||||||
|
logger.debug('New token for {0} character {1} saved. Evaluating ownership.'.format(instance.user,
|
||||||
|
instance.character_name))
|
||||||
if instance.user:
|
if instance.user:
|
||||||
query = Q(owner_hash=instance.character_owner_hash) & Q(user=instance.user)
|
query = Q(owner_hash=instance.character_owner_hash) & Q(user=instance.user)
|
||||||
else:
|
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()
|
CharacterOwnership.objects.filter(character__character_id=instance.character_id).exclude(query).delete()
|
||||||
# create character if needed
|
# create character if needed
|
||||||
if EveCharacter.objects.filter(character_id=instance.character_id).exists() is False:
|
if EveCharacter.objects.filter(character_id=instance.character_id).exists() is False:
|
||||||
|
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)
|
EveCharacter.objects.create_character(instance.character_id)
|
||||||
char = EveCharacter.objects.get(character_id=instance.character_id)
|
char = EveCharacter.objects.get(character_id=instance.character_id)
|
||||||
# check if we need to create ownership
|
# 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,
|
CharacterOwnership.objects.update_or_create(character=char,
|
||||||
defaults={'owner_hash': instance.character_owner_hash,
|
defaults={'owner_hash': instance.character_owner_hash,
|
||||||
'user': instance.user})
|
'user': instance.user})
|
||||||
@@ -92,19 +104,18 @@ def record_character_ownership(sender, instance, created, *args, **kwargs):
|
|||||||
@receiver(pre_delete, sender=CharacterOwnership)
|
@receiver(pre_delete, sender=CharacterOwnership)
|
||||||
def validate_main_character(sender, instance, *args, **kwargs):
|
def validate_main_character(sender, instance, *args, **kwargs):
|
||||||
if instance.user.profile.main_character == instance.character:
|
if instance.user.profile.main_character == instance.character:
|
||||||
|
logger.info("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
|
# clear main character as user no longer owns them
|
||||||
instance.user.profile.main_character = None
|
instance.user.profile.main_character = None
|
||||||
instance.user.profile.save()
|
instance.user.profile.save()
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_delete, sender=Token)
|
@receiver(post_delete, sender=Token)
|
||||||
def validate_main_character_token(sender, instance, *args, **kwargs):
|
def validate_ownership(sender, instance, *args, **kwargs):
|
||||||
if UserProfile.objects.filter(main_character__character_id=instance.character_id).exists():
|
if not Token.objects.filter(character_owner_hash=instance.character_owner_hash).filter(refresh_token__isnull=False).exists():
|
||||||
profile = UserProfile.objects.get(main_character__character_id=instance.character_id)
|
logger.info("No remaining tokens to validate ownership of character {0}. Revoking ownership.".format(instance.character_name))
|
||||||
if not Token.objects.filter(character_id=instance.character_id).filter(user=profile.user).exclude(pk=instance.pk).exists():
|
CharacterOwnership.objects.filter(owner_hash=instance.character_owner_hash).delete()
|
||||||
# clear main character as we can no longer verify ownership
|
|
||||||
profile.main_character = None
|
|
||||||
profile.save()
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_save, sender=User)
|
@receiver(pre_save, sender=User)
|
||||||
@@ -114,8 +125,11 @@ def assign_state_on_active_change(sender, instance, *args, **kwargs):
|
|||||||
old_instance = User.objects.get(pk=instance.pk)
|
old_instance = User.objects.get(pk=instance.pk)
|
||||||
if old_instance.is_active != instance.is_active:
|
if old_instance.is_active != instance.is_active:
|
||||||
if instance.is_active:
|
if instance.is_active:
|
||||||
|
logger.debug("User {0} has been activated. Assigning state.".format(instance))
|
||||||
instance.profile.assign_state()
|
instance.profile.assign_state()
|
||||||
else:
|
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.state = get_guest_state()
|
||||||
instance.profile.save(update_fields=['state'])
|
instance.profile.save(update_fields=['state'])
|
||||||
|
|
||||||
@@ -124,6 +138,20 @@ def assign_state_on_active_change(sender, instance, *args, **kwargs):
|
|||||||
def check_state_on_character_update(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
|
# if this is a main character updating, check that user's state
|
||||||
try:
|
try:
|
||||||
|
logger.debug("Character {0} has been saved. Assessing owner's state for changes.".format(instance))
|
||||||
instance.userprofile.assign_state()
|
instance.userprofile.assign_state()
|
||||||
except UserProfile.DoesNotExist:
|
except UserProfile.DoesNotExist:
|
||||||
|
logger.debug("Character {0} is not a main character. No state assessment required.".format(instance))
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_save, sender=CharacterOwnership)
|
||||||
|
def ownership_record_creation(sender, instance, created, *args, **kwargs):
|
||||||
|
if created:
|
||||||
|
records = OwnershipRecord.objects.filter(owner_hash=instance.owner_hash).filter(character=instance.character)
|
||||||
|
if records.exists():
|
||||||
|
if records[0].user == instance.user: # most recent record is sorted first
|
||||||
|
logger.debug("Already have ownership record of {0} by user {1}".format(instance.character, instance.user))
|
||||||
|
return
|
||||||
|
logger.info("Character {0} has a new owner {1}. Creating ownership record.".format(instance.character, instance.user))
|
||||||
|
OwnershipRecord.objects.create(user=instance.user, character=instance.character, owner_hash=instance.owner_hash)
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from esi.errors import TokenExpiredError, TokenInvalidError
|
from esi.errors import TokenExpiredError, TokenInvalidError, IncompleteResponseError
|
||||||
from esi.models import Token
|
from esi.models import Token
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
|
|
||||||
@@ -20,13 +20,19 @@ def check_character_ownership(owner_hash):
|
|||||||
except (TokenExpiredError, TokenInvalidError):
|
except (TokenExpiredError, TokenInvalidError):
|
||||||
t.delete()
|
t.delete()
|
||||||
continue
|
continue
|
||||||
|
except (KeyError, IncompleteResponseError):
|
||||||
if t.character_owner_hash == old_hash:
|
# We can't validate the hash hasn't changed but also can't assume it has. Abort for now.
|
||||||
|
logger.warning("Failed to validate owner hash of {0} due to problems contacting SSO servers.".format(
|
||||||
|
tokens[0].character_name))
|
||||||
break
|
break
|
||||||
else:
|
|
||||||
logger.info('Character %s has changed ownership. Revoking %s tokens.' % (t.character_name, tokens.count()))
|
if not t.character_owner_hash == old_hash:
|
||||||
|
logger.info(
|
||||||
|
'Character %s has changed ownership. Revoking %s tokens.' % (t.character_name, tokens.count()))
|
||||||
tokens.delete()
|
tokens.delete()
|
||||||
else:
|
break
|
||||||
|
|
||||||
|
if not Token.objects.filter(character_owner_hash=owner_hash).exists():
|
||||||
logger.info('No tokens found with owner hash %s. Revoking ownership.' % owner_hash)
|
logger.info('No tokens found with owner hash %s. Revoking ownership.' % owner_hash)
|
||||||
CharacterOwnership.objects.filter(owner_hash=owner_hash).delete()
|
CharacterOwnership.objects.filter(owner_hash=owner_hash).delete()
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<table class="table">
|
<table class="table">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-center"><img class="ra-avatar"
|
<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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% else %}
|
{% 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 %}
|
{% endif %}
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
<div class="col-xs-6">
|
<div class="col-xs-6">
|
||||||
@@ -102,8 +102,7 @@
|
|||||||
{% for ownership in request.user.character_ownerships.all %}
|
{% for ownership in request.user.character_ownerships.all %}
|
||||||
{% with ownership.character as char %}
|
{% with ownership.character as char %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-center"><img class="ra-avatar img-circle"
|
<td class="text-center"><img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}">
|
||||||
src="https://image.eveonline.com/Character/{{ char.character_id }}_32.jpg">
|
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">{{ char.character_name }}</td>
|
<td class="text-center">{{ char.character_name }}</td>
|
||||||
<td class="text-center">{{ char.corporation_name }}</td>
|
<td class="text-center">{{ char.corporation_name }}</td>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
body {
|
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;
|
-webkit-background-size: cover;
|
||||||
-moz-background-size: cover;
|
-moz-background-size: cover;
|
||||||
-o-background-size: cover;
|
-o-background-size: cover;
|
||||||
@@ -48,4 +48,4 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
<form action="{% url 'set_language' %}" method="post">
|
<form action="{% url 'set_language' %}" method="post">
|
||||||
{% csrf_token %}
|
{% 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">
|
<select onchange="this.form.submit()" class="form-control" id="lang-select" name="language">
|
||||||
{% get_language_info_list for LANGUAGES as languages %}
|
{% get_language_info_list for LANGUAGES as languages %}
|
||||||
{% for language in languages %}
|
{% for language in languages %}
|
||||||
@@ -12,4 +11,4 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</form>
|
</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:
|
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).
|
This link will expire in {{ expiration_days }} day(s).
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,70 @@
|
|||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
from io import StringIO
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from allianceauth.tests.auth_utils import AuthUtils
|
from allianceauth.tests.auth_utils import AuthUtils
|
||||||
from .models import CharacterOwnership, UserProfile, State, get_guest_state
|
from .models import CharacterOwnership, UserProfile, State, get_guest_state, OwnershipRecord
|
||||||
from .backends import StateBackend
|
from .backends import StateBackend
|
||||||
from .tasks import check_character_ownership
|
from .tasks import check_character_ownership
|
||||||
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo, EveAllianceInfo
|
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo, EveAllianceInfo
|
||||||
from esi.models import Token
|
from esi.models import Token
|
||||||
|
from esi.errors import IncompleteResponseError
|
||||||
|
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 django.core.management import call_command
|
||||||
|
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):
|
class BackendTestCase(TestCase):
|
||||||
@@ -35,6 +92,7 @@ class BackendTestCase(TestCase):
|
|||||||
corporation_ticker='CORP',
|
corporation_ticker='CORP',
|
||||||
)
|
)
|
||||||
cls.user = AuthUtils.create_user('test_user', disconnect_signals=True)
|
cls.user = AuthUtils.create_user('test_user', disconnect_signals=True)
|
||||||
|
cls.old_user = AuthUtils.create_user('old_user', disconnect_signals=True)
|
||||||
AuthUtils.disconnect_signals()
|
AuthUtils.disconnect_signals()
|
||||||
CharacterOwnership.objects.create(user=cls.user, character=cls.main_character, owner_hash='1')
|
CharacterOwnership.objects.create(user=cls.user, character=cls.main_character, owner_hash='1')
|
||||||
CharacterOwnership.objects.create(user=cls.user, character=cls.alt_character, owner_hash='2')
|
CharacterOwnership.objects.create(user=cls.user, character=cls.alt_character, owner_hash='2')
|
||||||
@@ -58,6 +116,14 @@ class BackendTestCase(TestCase):
|
|||||||
self.assertEqual(user.username, 'Unclaimed_Character')
|
self.assertEqual(user.username, 'Unclaimed_Character')
|
||||||
self.assertEqual(user.profile.main_character, self.unclaimed_character)
|
self.assertEqual(user.profile.main_character, self.unclaimed_character)
|
||||||
|
|
||||||
|
def test_authenticate_character_record(self):
|
||||||
|
t = Token(character_id=self.unclaimed_character.character_id, character_name=self.unclaimed_character.character_name, character_owner_hash='4')
|
||||||
|
record = OwnershipRecord.objects.create(user=self.old_user, character=self.unclaimed_character, owner_hash='4')
|
||||||
|
user = StateBackend().authenticate(t)
|
||||||
|
self.assertEqual(user, self.old_user)
|
||||||
|
self.assertTrue(CharacterOwnership.objects.filter(owner_hash='4', user=self.old_user).exists())
|
||||||
|
self.assertTrue(user.profile.main_character)
|
||||||
|
|
||||||
def test_iterate_username(self):
|
def test_iterate_username(self):
|
||||||
t = Token(character_id=self.unclaimed_character.character_id,
|
t = Token(character_id=self.unclaimed_character.character_id,
|
||||||
character_name=self.unclaimed_character.character_name, character_owner_hash='3')
|
character_name=self.unclaimed_character.character_name, character_owner_hash='3')
|
||||||
@@ -130,28 +196,6 @@ class CharacterOwnershipTestCase(TestCase):
|
|||||||
self.user = User.objects.get(pk=self.user.pk)
|
self.user = User.objects.get(pk=self.user.pk)
|
||||||
self.assertIsNone(self.user.profile.main_character)
|
self.assertIsNone(self.user.profile.main_character)
|
||||||
|
|
||||||
@mock.patch('esi.models.Token.update_token_data')
|
|
||||||
def test_character_ownership_check(self, update_token_data):
|
|
||||||
t = Token.objects.create(
|
|
||||||
user=self.user,
|
|
||||||
character_id=self.character.character_id,
|
|
||||||
character_name=self.character.character_name,
|
|
||||||
character_owner_hash='1',
|
|
||||||
)
|
|
||||||
co = CharacterOwnership.objects.get(owner_hash='1')
|
|
||||||
check_character_ownership(co.owner_hash)
|
|
||||||
self.assertTrue(CharacterOwnership.objects.filter(owner_hash='1').exists())
|
|
||||||
|
|
||||||
t.character_owner_hash = '2'
|
|
||||||
t.save()
|
|
||||||
check_character_ownership(co.owner_hash)
|
|
||||||
self.assertFalse(CharacterOwnership.objects.filter(owner_hash='1').exists())
|
|
||||||
|
|
||||||
t.delete()
|
|
||||||
co = CharacterOwnership.objects.create(user=self.user, character=self.character, owner_hash='3')
|
|
||||||
check_character_ownership(co.owner_hash)
|
|
||||||
self.assertFalse(CharacterOwnership.objects.filter(owner_hash='3').exists())
|
|
||||||
|
|
||||||
|
|
||||||
class StateTestCase(TestCase):
|
class StateTestCase(TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -286,3 +330,73 @@ class StateTestCase(TestCase):
|
|||||||
self.user.save()
|
self.user.save()
|
||||||
self._refresh_user()
|
self._refresh_user()
|
||||||
self.assertEquals(self.user.profile.state, self.member_state)
|
self.assertEquals(self.user.profile.state, self.member_state)
|
||||||
|
|
||||||
|
|
||||||
|
class CharacterOwnershipCheckTestCase(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
cls.user = AuthUtils.create_user('test_user', disconnect_signals=True)
|
||||||
|
AuthUtils.add_main_character(cls.user, 'Test Character', '1', corp_id='1', alliance_id='1',
|
||||||
|
corp_name='Test Corp', alliance_name='Test Alliance')
|
||||||
|
cls.character = EveCharacter.objects.get(character_id='1')
|
||||||
|
cls.token = Token.objects.create(
|
||||||
|
user=cls.user,
|
||||||
|
character_id='1',
|
||||||
|
character_name='Test',
|
||||||
|
character_owner_hash='1',
|
||||||
|
)
|
||||||
|
cls.ownership = CharacterOwnership.objects.get(character=cls.character)
|
||||||
|
|
||||||
|
@mock.patch(MODULE_PATH + '.tasks.Token.update_token_data')
|
||||||
|
def test_no_change_owner_hash(self, update_token_data):
|
||||||
|
# makes sure the ownership isn't delete if owner hash hasn't changed
|
||||||
|
check_character_ownership(self.ownership)
|
||||||
|
self.assertTrue(CharacterOwnership.objects.filter(user=self.user).filter(character=self.character).exists())
|
||||||
|
|
||||||
|
@mock.patch(MODULE_PATH + '.tasks.Token.update_token_data')
|
||||||
|
def test_unable_to_update_token_data(self, update_token_data):
|
||||||
|
# makes sure ownerships and tokens aren't hellpurged when there's problems with the SSO servers
|
||||||
|
update_token_data.side_effect = IncompleteResponseError()
|
||||||
|
check_character_ownership(self.ownership)
|
||||||
|
self.assertTrue(CharacterOwnership.objects.filter(user=self.user).filter(character=self.character).exists())
|
||||||
|
|
||||||
|
update_token_data.side_effect = KeyError()
|
||||||
|
check_character_ownership(self.ownership)
|
||||||
|
self.assertTrue(CharacterOwnership.objects.filter(user=self.user).filter(character=self.character).exists())
|
||||||
|
|
||||||
|
@mock.patch(MODULE_PATH + '.tasks.Token.update_token_data')
|
||||||
|
@mock.patch(MODULE_PATH + '.tasks.Token.delete')
|
||||||
|
@mock.patch(MODULE_PATH + '.tasks.Token.objects.exists')
|
||||||
|
@mock.patch(MODULE_PATH + '.tasks.CharacterOwnership.objects.filter')
|
||||||
|
def test_owner_hash_changed(self, filter, exists, delete, update_token_data):
|
||||||
|
# makes sure the ownership is revoked when owner hash changes
|
||||||
|
filter.return_value.exists.return_value = False
|
||||||
|
check_character_ownership(self.ownership)
|
||||||
|
self.assertTrue(filter.return_value.delete.called)
|
||||||
|
|
||||||
|
|
||||||
|
class ManagementCommandTestCase(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
cls.user = AuthUtils.create_user('test user', disconnect_signals=True)
|
||||||
|
AuthUtils.add_main_character(cls.user, 'test character', '1', '2', 'test corp', 'test')
|
||||||
|
character = UserProfile.objects.get(user=cls.user).main_character
|
||||||
|
CharacterOwnership.objects.create(user=cls.user, character=character, owner_hash='test')
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.stdout = StringIO()
|
||||||
|
|
||||||
|
def test_ownership(self):
|
||||||
|
call_command('checkmains', stdout=self.stdout)
|
||||||
|
self.assertFalse(UserProfile.objects.filter(main_character__isnull=True).count())
|
||||||
|
self.assertNotIn(self.user.username, self.stdout.getvalue())
|
||||||
|
self.assertIn('All main characters', self.stdout.getvalue())
|
||||||
|
|
||||||
|
def test_no_ownership(self):
|
||||||
|
user = AuthUtils.create_user('v1 user', disconnect_signals=True)
|
||||||
|
AuthUtils.add_main_character(user, 'v1 character', '10', '20', 'test corp', 'test')
|
||||||
|
self.assertFalse(UserProfile.objects.filter(main_character__isnull=True).count())
|
||||||
|
|
||||||
|
call_command('checkmains', stdout=self.stdout)
|
||||||
|
self.assertEqual(UserProfile.objects.filter(main_character__isnull=True).count(), 1)
|
||||||
|
self.assertIn(user.username, self.stdout.getvalue())
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from django.urls import reverse
|
|||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from esi.decorators import token_required
|
from esi.decorators import token_required
|
||||||
|
from esi.models import Token
|
||||||
from registration.backends.hmac.views import RegistrationView as BaseRegistrationView, \
|
from registration.backends.hmac.views import RegistrationView as BaseRegistrationView, \
|
||||||
ActivationView as BaseActivationView, REGISTRATION_SALT
|
ActivationView as BaseActivationView, REGISTRATION_SALT
|
||||||
from registration.signals import user_registered
|
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)
|
@token_required(new=True, scopes=settings.LOGIN_TOKEN_SCOPES)
|
||||||
def sso_login(request, token):
|
def sso_login(request, token):
|
||||||
user = authenticate(token=token)
|
user = authenticate(token=token)
|
||||||
if user and user.is_active:
|
if user:
|
||||||
login(request, user)
|
token.user = user
|
||||||
return redirect(request.POST.get('next', request.GET.get('next', 'authentication:dashboard')))
|
if Token.objects.exclude(pk=token.pk).equivalent_to(token).require_valid().exists():
|
||||||
elif user and not user.email:
|
token.delete()
|
||||||
# Store the new user PK in the session to enable us to identify the registering user in Step 2
|
else:
|
||||||
request.session['registration_uid'] = user.pk
|
token.save()
|
||||||
# Go to Step 2
|
if user.is_active:
|
||||||
return redirect('registration_register')
|
login(request, user)
|
||||||
else:
|
return redirect(request.POST.get('next', request.GET.get('next', 'authentication:dashboard')))
|
||||||
messages.error(request, _('Unable to authenticate as the selected character.'))
|
elif not user.email:
|
||||||
return redirect(settings.LOGIN_URL)
|
# 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
|
# Step 2
|
||||||
@@ -89,20 +95,33 @@ class RegistrationView(BaseRegistrationView):
|
|||||||
form_class = RegistrationForm
|
form_class = RegistrationForm
|
||||||
success_url = 'authentication:dashboard'
|
success_url = 'authentication:dashboard'
|
||||||
|
|
||||||
def dispatch(self, *args, **kwargs):
|
def get_success_url(self, user):
|
||||||
|
if not getattr(settings, 'REGISTRATION_VERIFY_EMAIL', True):
|
||||||
|
return 'authentication:dashboard', (), {}
|
||||||
|
return super().get_success_url(user)
|
||||||
|
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
# We're storing a key in the session to pass user information from OAuth response. Make sure it's there.
|
# We're storing a key in the session to pass user information from OAuth response. Make sure it's there.
|
||||||
if not self.request.session.get('registration_uid', None) or not User.objects.filter(
|
if not self.request.session.get('registration_uid', None) or not User.objects.filter(
|
||||||
pk=self.request.session.get('registration_uid')).exists():
|
pk=self.request.session.get('registration_uid')).exists():
|
||||||
messages.error(self.request, _('Registration token has expired.'))
|
messages.error(self.request, _('Registration token has expired.'))
|
||||||
return redirect(settings.LOGIN_URL)
|
return redirect(settings.LOGIN_URL)
|
||||||
return super(RegistrationView, self).dispatch(*args, **kwargs)
|
if not getattr(settings, 'REGISTRATION_VERIFY_EMAIL', True):
|
||||||
|
# Keep the request so the user can be automagically logged in.
|
||||||
|
setattr(self, 'request', request)
|
||||||
|
return super(RegistrationView, self).dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def register(self, form):
|
def register(self, form):
|
||||||
user = User.objects.get(pk=self.request.session.get('registration_uid'))
|
user = User.objects.get(pk=self.request.session.get('registration_uid'))
|
||||||
user.email = form.cleaned_data['email']
|
user.email = form.cleaned_data['email']
|
||||||
user_registered.send(self.__class__, user=user, request=self.request)
|
user_registered.send(self.__class__, user=user, request=self.request)
|
||||||
# Go to Step 3
|
if getattr(settings, 'REGISTRATION_VERIFY_EMAIL', True):
|
||||||
self.send_activation_email(user)
|
# Go to Step 3
|
||||||
|
self.send_activation_email(user)
|
||||||
|
else:
|
||||||
|
user.is_active = True
|
||||||
|
user.save()
|
||||||
|
login(self.request, user, 'allianceauth.authentication.backends.StateBackend')
|
||||||
return user
|
return user
|
||||||
|
|
||||||
def get_activation_key(self, user):
|
def get_activation_key(self, user):
|
||||||
|
|||||||
@@ -40,9 +40,8 @@ class CorpStats(models.Model):
|
|||||||
c = self.token.get_esi_client(spec_file=SWAGGER_SPEC_PATH)
|
c = self.token.get_esi_client(spec_file=SWAGGER_SPEC_PATH)
|
||||||
assert c.Character.get_characters_character_id(character_id=self.token.character_id).result()[
|
assert c.Character.get_characters_character_id(character_id=self.token.character_id).result()[
|
||||||
'corporation_id'] == int(self.corp.corporation_id)
|
'corporation_id'] == int(self.corp.corporation_id)
|
||||||
members = c.Corporation.get_corporations_corporation_id_members(
|
member_ids = c.Corporation.get_corporations_corporation_id_members(
|
||||||
corporation_id=self.corp.corporation_id).result()
|
corporation_id=self.corp.corporation_id).result()
|
||||||
member_ids = [m['character_id'] for m in members]
|
|
||||||
|
|
||||||
# requesting too many ids per call results in a HTTP400
|
# requesting too many ids per call results in a HTTP400
|
||||||
# the swagger spec doesn't have a maxItems count
|
# the swagger spec doesn't have a maxItems count
|
||||||
@@ -121,8 +120,11 @@ class CorpStats(models.Model):
|
|||||||
m.main_character and int(m.main_character.character_id) == int(
|
m.main_character and int(m.main_character.character_id) == int(
|
||||||
m.character_id)])
|
m.character_id)])
|
||||||
|
|
||||||
|
def visible_to(self, user):
|
||||||
|
return CorpStats.objects.filter(pk=self.pk).visible_to(user).exists()
|
||||||
|
|
||||||
def can_update(self, user):
|
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):
|
def corp_logo(self, size=128):
|
||||||
return "https://image.eveonline.com/Corporation/%s_%s.png" % (self.corp.corporation_id, size)
|
return "https://image.eveonline.com/Corporation/%s_%s.png" % (self.corp.corporation_id, size)
|
||||||
@@ -179,4 +181,4 @@ class CorpMember(models.Model):
|
|||||||
if item.startswith('portrait_url_'):
|
if item.startswith('portrait_url_'):
|
||||||
size = item.strip('portrait_url_')
|
size = item.strip('portrait_url_')
|
||||||
return self.portrait_url(size)
|
return self.portrait_url(size)
|
||||||
return super(CorpMember, self).__getattr__(item)
|
return self.__getattribute__(item)
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
|||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
from allianceauth.corputils import CorpStats
|
from allianceauth.corputils.models import CorpStats
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
|
|||||||
@@ -9,9 +9,9 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td class="text-center col-lg-6
|
<td class="text-center col-lg-6
|
||||||
{% if corpstats.corp.alliance %}{% else %}col-lg-offset-3{% endif %}"><img
|
{% 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 %}
|
{% 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>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
@@ -70,6 +70,7 @@
|
|||||||
{% for alt in main.alts %}
|
{% for alt in main.alts %}
|
||||||
{% if forloop.first %}
|
{% if forloop.first %}
|
||||||
<tr>
|
<tr>
|
||||||
|
<th></th>
|
||||||
<th class="text-center">{% trans "Character" %}</th>
|
<th class="text-center">{% trans "Character" %}</th>
|
||||||
<th class="text-center">{% trans "Corporation" %}</th>
|
<th class="text-center">{% trans "Corporation" %}</th>
|
||||||
<th class="text-center">{% trans "Alliance" %}</th>
|
<th class="text-center">{% trans "Alliance" %}</th>
|
||||||
@@ -77,10 +78,15 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-center">{{ alt.character_name }}</td>
|
<td class="text-center" style="width:5%">
|
||||||
<td class="text-center">{{ alt.corporation_name }}</td>
|
<div class="thumbnail" style="border: 0 none; box-shadow: none; background: transparent;">
|
||||||
<td class="text-center">{{ alt.alliance_name }}</td>
|
<img src="https://image.eveonline.com/Character/{{ alt.character_id }}_32.jpg" class="img-circle">
|
||||||
<td class="text-center">
|
</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 }}/"
|
<a href="https://zkillboard.com/character/{{ alt.character_id }}/"
|
||||||
class="label label-danger" target="_blank">
|
class="label label-danger" target="_blank">
|
||||||
{% trans "Killboard" %}
|
{% trans "Killboard" %}
|
||||||
@@ -175,9 +181,25 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block extra_script %}
|
{% block extra_script %}
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
$('#table-mains').DataTable();
|
$('#table-mains').DataTable({
|
||||||
$('#table-members').DataTable();
|
"columnDefs": [
|
||||||
$('#table-unregistered').DataTable();
|
{ "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 %}
|
{% endblock %}
|
||||||
@@ -85,7 +85,7 @@ class CorpStatsUpdateTestCase(TestCase):
|
|||||||
@mock.patch('esi.clients.SwaggerClient')
|
@mock.patch('esi.clients.SwaggerClient')
|
||||||
def test_update_add_member(self, SwaggerClient):
|
def test_update_add_member(self, SwaggerClient):
|
||||||
SwaggerClient.from_spec.return_value.Character.get_characters_character_id.return_value.result.return_value = {'corporation_id': 2}
|
SwaggerClient.from_spec.return_value.Character.get_characters_character_id.return_value.result.return_value = {'corporation_id': 2}
|
||||||
SwaggerClient.from_spec.return_value.Corporation.get_corporations_corporation_id_members.return_value.result.return_value = [{'character_id': 1}]
|
SwaggerClient.from_spec.return_value.Corporation.get_corporations_corporation_id_members.return_value.result.return_value = [1]
|
||||||
SwaggerClient.from_spec.return_value.Character.get_characters_names.return_value.result.return_value = [{'character_id': 1, 'character_name': 'test character'}]
|
SwaggerClient.from_spec.return_value.Character.get_characters_names.return_value.result.return_value = [{'character_id': 1, 'character_name': 'test character'}]
|
||||||
self.corpstats.update()
|
self.corpstats.update()
|
||||||
self.assertTrue(CorpMember.objects.filter(character_id='1', character_name='test character', corpstats=self.corpstats).exists())
|
self.assertTrue(CorpMember.objects.filter(character_id='1', character_name='test character', corpstats=self.corpstats).exists())
|
||||||
@@ -94,7 +94,7 @@ class CorpStatsUpdateTestCase(TestCase):
|
|||||||
def test_update_remove_member(self, SwaggerClient):
|
def test_update_remove_member(self, SwaggerClient):
|
||||||
CorpMember.objects.create(character_id='2', character_name='old test character', corpstats=self.corpstats)
|
CorpMember.objects.create(character_id='2', character_name='old test character', corpstats=self.corpstats)
|
||||||
SwaggerClient.from_spec.return_value.Character.get_characters_character_id.return_value.result.return_value = {'corporation_id': 2}
|
SwaggerClient.from_spec.return_value.Character.get_characters_character_id.return_value.result.return_value = {'corporation_id': 2}
|
||||||
SwaggerClient.from_spec.return_value.Corporation.get_corporations_corporation_id_members.return_value.result.return_value = [{'character_id': 1}]
|
SwaggerClient.from_spec.return_value.Corporation.get_corporations_corporation_id_members.return_value.result.return_value = [1]
|
||||||
SwaggerClient.from_spec.return_value.Character.get_characters_names.return_value.result.return_value = [{'character_id': 1, 'character_name': 'test character'}]
|
SwaggerClient.from_spec.return_value.Character.get_characters_names.return_value.result.return_value = [{'character_id': 1, 'character_name': 'test character'}]
|
||||||
self.corpstats.update()
|
self.corpstats.update()
|
||||||
self.assertFalse(CorpMember.objects.filter(character_id='2', corpstats=self.corpstats).exists())
|
self.assertFalse(CorpMember.objects.filter(character_id='2', corpstats=self.corpstats).exists())
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
def get_users_for_state(state: State):
|
def get_users_for_state(state: State):
|
||||||
return User.objects.select_related('profile').prefetch_related('profile__main_character')\
|
return User.objects.select_related('profile').prefetch_related('profile__main_character')\
|
||||||
.filter(profile__state__pk=state.pk)
|
.filter(profile__state_id=state.pk)
|
||||||
|
|
||||||
|
|
||||||
class AutogroupsConfigManager(models.Manager):
|
class AutogroupsConfigManager(models.Manager):
|
||||||
@@ -39,7 +39,12 @@ class AutogroupsConfigManager(models.Manager):
|
|||||||
if state is None:
|
if state is None:
|
||||||
state = user.profile.state
|
state = user.profile.state
|
||||||
for config in self.filter(states=state):
|
for config in self.filter(states=state):
|
||||||
config.update_group_membership_for_user(user)
|
# grant user new groups for their state
|
||||||
|
config.update_group_membership_for_user(user)
|
||||||
|
for config in self.exclude(states=state):
|
||||||
|
# ensure user does not have groups from previous state
|
||||||
|
config.remove_user_from_alliance_groups(user)
|
||||||
|
config.remove_user_from_corp_groups(user)
|
||||||
|
|
||||||
|
|
||||||
class AutogroupsConfig(models.Model):
|
class AutogroupsConfig(models.Model):
|
||||||
@@ -119,8 +124,9 @@ class AutogroupsConfig(models.Model):
|
|||||||
return
|
return
|
||||||
group = self.get_alliance_group(alliance)
|
group = self.get_alliance_group(alliance)
|
||||||
except EveAllianceInfo.DoesNotExist:
|
except EveAllianceInfo.DoesNotExist:
|
||||||
logger.warning('User {} main characters alliance does not exist in the database.'
|
logger.debug('User {} main characters alliance does not exist in the database. Creating.'.format(user))
|
||||||
' Group membership not updated'.format(user))
|
alliance = EveAllianceInfo.objects.create_alliance(user.profile.main_character.alliance_id)
|
||||||
|
group = self.get_alliance_group(alliance)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
logger.warning('User {} does not have a main character. Group membership not updated'.format(user))
|
logger.warning('User {} does not have a main character. Group membership not updated'.format(user))
|
||||||
finally:
|
finally:
|
||||||
@@ -139,8 +145,9 @@ class AutogroupsConfig(models.Model):
|
|||||||
corp = user.profile.main_character.corporation
|
corp = user.profile.main_character.corporation
|
||||||
group = self.get_corp_group(corp)
|
group = self.get_corp_group(corp)
|
||||||
except EveCorporationInfo.DoesNotExist:
|
except EveCorporationInfo.DoesNotExist:
|
||||||
logger.warning('User {} main characters corporation does not exist in the database.'
|
logger.debug('User {} main characters corporation does not exist in the database. Creating.'.format(user))
|
||||||
' Group membership not updated'.format(user))
|
corp = EveCorporationInfo.objects.create_corporation(user.profile.main_character.corporation_id)
|
||||||
|
group = self.get_corp_group(corp)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
logger.warning('User {} does not have a main character. Group membership not updated'.format(user))
|
logger.warning('User {} does not have a main character. Group membership not updated'.format(user))
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import logging
|
|||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.db.models.signals import pre_save, post_save, pre_delete, m2m_changed
|
from django.db.models.signals import pre_save, post_save, pre_delete, m2m_changed
|
||||||
from allianceauth.authentication.models import UserProfile, State
|
from allianceauth.authentication.models import UserProfile, State
|
||||||
|
from allianceauth.eveonline.models import EveCharacter
|
||||||
|
|
||||||
from .models import AutogroupsConfig
|
from .models import AutogroupsConfig
|
||||||
|
|
||||||
@@ -45,9 +46,7 @@ def check_groups_on_profile_update(sender, instance, created, *args, **kwargs):
|
|||||||
"""
|
"""
|
||||||
Trigger check when main character or state changes.
|
Trigger check when main character or state changes.
|
||||||
"""
|
"""
|
||||||
update_fields = kwargs.pop('update_fields', []) or []
|
AutogroupsConfig.objects.update_groups_for_user(instance.user)
|
||||||
if 'main_character' in update_fields or 'state' in update_fields:
|
|
||||||
AutogroupsConfig.objects.update_groups_for_user(instance.user)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(m2m_changed, sender=AutogroupsConfig.states.through)
|
@receiver(m2m_changed, sender=AutogroupsConfig.states.through)
|
||||||
@@ -64,3 +63,13 @@ def autogroups_states_changed(sender, instance, action, reverse, model, pk_set,
|
|||||||
except State.DoesNotExist:
|
except State.DoesNotExist:
|
||||||
# Deleted States handled by the profile state change
|
# Deleted States handled by the profile state change
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_save, sender=EveCharacter)
|
||||||
|
def check_groups_on_character_update(sender, instance, created, *args, **kwargs):
|
||||||
|
if not created:
|
||||||
|
try:
|
||||||
|
profile = UserProfile.objects.prefetch_related('user').get(main_character_id=instance.pk)
|
||||||
|
AutogroupsConfig.objects.update_groups_for_user(profile.user)
|
||||||
|
except UserProfile.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
from unittest import mock
|
from unittest import mock
|
||||||
from django.db.models.signals import pre_save, post_save, pre_delete, m2m_changed
|
from django.db.models.signals import pre_save, post_save, pre_delete, m2m_changed
|
||||||
from allianceauth.authentication.models import UserProfile
|
from allianceauth.authentication.models import UserProfile
|
||||||
from allianceauth.authentication.signals import state_changed
|
from allianceauth.authentication.signals import reassess_on_profile_save
|
||||||
from allianceauth.eveonline.models import EveCharacter
|
|
||||||
from .. import signals
|
from .. import signals
|
||||||
from ..models import AutogroupsConfig
|
from ..models import AutogroupsConfig
|
||||||
|
|
||||||
@@ -14,6 +13,7 @@ def patch(target, *args, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
def connect_signals():
|
def connect_signals():
|
||||||
|
post_save.connect(receiver=reassess_on_profile_save, sender=UserProfile)
|
||||||
pre_save.connect(receiver=signals.pre_save_config, sender=AutogroupsConfig)
|
pre_save.connect(receiver=signals.pre_save_config, sender=AutogroupsConfig)
|
||||||
pre_delete.connect(receiver=signals.pre_delete_config, sender=AutogroupsConfig)
|
pre_delete.connect(receiver=signals.pre_delete_config, sender=AutogroupsConfig)
|
||||||
post_save.connect(receiver=signals.check_groups_on_profile_update, sender=UserProfile)
|
post_save.connect(receiver=signals.check_groups_on_profile_update, sender=UserProfile)
|
||||||
@@ -21,6 +21,7 @@ def connect_signals():
|
|||||||
|
|
||||||
|
|
||||||
def disconnect_signals():
|
def disconnect_signals():
|
||||||
|
post_save.disconnect(receiver=reassess_on_profile_save, sender=UserProfile)
|
||||||
pre_save.disconnect(receiver=signals.pre_save_config, sender=AutogroupsConfig)
|
pre_save.disconnect(receiver=signals.pre_save_config, sender=AutogroupsConfig)
|
||||||
pre_delete.disconnect(receiver=signals.pre_delete_config, sender=AutogroupsConfig)
|
pre_delete.disconnect(receiver=signals.pre_delete_config, sender=AutogroupsConfig)
|
||||||
post_save.disconnect(receiver=signals.check_groups_on_profile_update, sender=UserProfile)
|
post_save.disconnect(receiver=signals.check_groups_on_profile_update, sender=UserProfile)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from . import patch
|
|||||||
|
|
||||||
class AutogroupsConfigManagerTestCase(TestCase):
|
class AutogroupsConfigManagerTestCase(TestCase):
|
||||||
|
|
||||||
def test_update_groups_for_state(self, ):
|
def test_update_groups_for_state(self):
|
||||||
member = AuthUtils.create_member('test member')
|
member = AuthUtils.create_member('test member')
|
||||||
obj = AutogroupsConfig.objects.create()
|
obj = AutogroupsConfig.objects.create()
|
||||||
obj.states.add(member.profile.state)
|
obj.states.add(member.profile.state)
|
||||||
@@ -32,3 +32,23 @@ class AutogroupsConfigManagerTestCase(TestCase):
|
|||||||
self.assertEqual(update_group_membership_for_user.call_count, 1)
|
self.assertEqual(update_group_membership_for_user.call_count, 1)
|
||||||
args, kwargs = update_group_membership_for_user.call_args
|
args, kwargs = update_group_membership_for_user.call_args
|
||||||
self.assertEqual(args[0], member)
|
self.assertEqual(args[0], member)
|
||||||
|
|
||||||
|
@patch('.models.AutogroupsConfig.update_group_membership_for_user')
|
||||||
|
@patch('.models.AutogroupsConfig.remove_user_from_alliance_groups')
|
||||||
|
@patch('.models.AutogroupsConfig.remove_user_from_corp_groups')
|
||||||
|
def test_update_groups_no_config(self, remove_corp, remove_alliance, update_groups):
|
||||||
|
member = AuthUtils.create_member('test member')
|
||||||
|
obj = AutogroupsConfig.objects.create()
|
||||||
|
|
||||||
|
# Corp and alliance groups should be removed from users if their state has no config
|
||||||
|
AutogroupsConfig.objects.update_groups_for_user(member)
|
||||||
|
|
||||||
|
self.assertFalse(update_groups.called)
|
||||||
|
self.assertTrue(remove_alliance.called)
|
||||||
|
self.assertTrue(remove_corp.called)
|
||||||
|
|
||||||
|
# The normal group assignment should occur if there state has a config
|
||||||
|
obj.states.add(member.profile.state)
|
||||||
|
AutogroupsConfig.objects.update_groups_for_user(member)
|
||||||
|
|
||||||
|
self.assertTrue(update_groups.called)
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ class AutogroupsConfigTestCase(TestCase):
|
|||||||
# Disconnect signals
|
# Disconnect signals
|
||||||
disconnect_signals()
|
disconnect_signals()
|
||||||
|
|
||||||
self.member = AuthUtils.create_member('test user')
|
|
||||||
|
|
||||||
state = AuthUtils.get_member_state()
|
state = AuthUtils.get_member_state()
|
||||||
|
|
||||||
self.alliance = EveAllianceInfo.objects.create(
|
self.alliance = EveAllianceInfo.objects.create(
|
||||||
@@ -38,6 +36,8 @@ class AutogroupsConfigTestCase(TestCase):
|
|||||||
state.member_alliances.add(self.alliance)
|
state.member_alliances.add(self.alliance)
|
||||||
state.member_corporations.add(self.corp)
|
state.member_corporations.add(self.corp)
|
||||||
|
|
||||||
|
self.member = AuthUtils.create_member('test user')
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
# Reconnect signals
|
# Reconnect signals
|
||||||
connect_signals()
|
connect_signals()
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.contrib.auth.models import Group, User
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
from allianceauth.tests.auth_utils import AuthUtils
|
from allianceauth.tests.auth_utils import AuthUtils
|
||||||
|
|
||||||
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo, EveAllianceInfo
|
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo, EveAllianceInfo
|
||||||
|
|
||||||
from ..models import AutogroupsConfig, ManagedAllianceGroup
|
from ..models import AutogroupsConfig
|
||||||
|
|
||||||
from . import patch, disconnect_signals, connect_signals
|
from . import patch, disconnect_signals, connect_signals
|
||||||
|
|
||||||
@@ -13,8 +13,6 @@ from . import patch, disconnect_signals, connect_signals
|
|||||||
class SignalsTestCase(TestCase):
|
class SignalsTestCase(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
disconnect_signals()
|
disconnect_signals()
|
||||||
self.member = AuthUtils.create_member('test user')
|
|
||||||
|
|
||||||
state = AuthUtils.get_member_state()
|
state = AuthUtils.get_member_state()
|
||||||
|
|
||||||
self.char = EveCharacter.objects.create(
|
self.char = EveCharacter.objects.create(
|
||||||
@@ -27,9 +25,6 @@ class SignalsTestCase(TestCase):
|
|||||||
alliance_name='alliance name',
|
alliance_name='alliance name',
|
||||||
)
|
)
|
||||||
|
|
||||||
self.member.profile.main_character = self.char
|
|
||||||
self.member.profile.save()
|
|
||||||
|
|
||||||
self.alliance = EveAllianceInfo.objects.create(
|
self.alliance = EveAllianceInfo.objects.create(
|
||||||
alliance_id='3456',
|
alliance_id='3456',
|
||||||
alliance_name='alliance name',
|
alliance_name='alliance name',
|
||||||
@@ -47,13 +42,17 @@ class SignalsTestCase(TestCase):
|
|||||||
|
|
||||||
state.member_alliances.add(self.alliance)
|
state.member_alliances.add(self.alliance)
|
||||||
state.member_corporations.add(self.corp)
|
state.member_corporations.add(self.corp)
|
||||||
|
|
||||||
|
self.member = AuthUtils.create_member('test user')
|
||||||
|
self.member.profile.main_character = self.char
|
||||||
|
self.member.profile.save()
|
||||||
|
|
||||||
connect_signals()
|
connect_signals()
|
||||||
|
|
||||||
@patch('.models.AutogroupsConfigManager.update_groups_for_user')
|
@patch('.models.AutogroupsConfigManager.update_groups_for_user')
|
||||||
def test_check_groups_on_profile_update_state(self, update_groups_for_user):
|
def test_check_groups_on_profile_update_state(self, update_groups_for_user):
|
||||||
# Trigger signal
|
# Trigger signal
|
||||||
self.member.profile.state = AuthUtils.get_guest_state()
|
self.member.profile.assign_state(state=AuthUtils.get_guest_state())
|
||||||
self.member.profile.save()
|
|
||||||
|
|
||||||
self.assertTrue(update_groups_for_user.called)
|
self.assertTrue(update_groups_for_user.called)
|
||||||
self.assertEqual(update_groups_for_user.call_count, 1)
|
self.assertEqual(update_groups_for_user.call_count, 1)
|
||||||
@@ -71,10 +70,10 @@ class SignalsTestCase(TestCase):
|
|||||||
alliance_id='3456',
|
alliance_id='3456',
|
||||||
alliance_name='alliance name',
|
alliance_name='alliance name',
|
||||||
)
|
)
|
||||||
|
|
||||||
# Trigger signal
|
# Trigger signal
|
||||||
self.member.profile.main_character = char
|
self.member.profile.main_character = char
|
||||||
self.member.profile.save()
|
self.member.profile.save()
|
||||||
|
|
||||||
self.assertTrue(update_groups_for_user.called)
|
self.assertTrue(update_groups_for_user.called)
|
||||||
self.assertEqual(update_groups_for_user.call_count, 1)
|
self.assertEqual(update_groups_for_user.call_count, 1)
|
||||||
args, kwargs = update_groups_for_user.call_args
|
args, kwargs = update_groups_for_user.call_args
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ class EveCharacterManager(models.Manager):
|
|||||||
corporation_ticker=character.corp.ticker,
|
corporation_ticker=character.corp.ticker,
|
||||||
alliance_id=character.alliance.id,
|
alliance_id=character.alliance.id,
|
||||||
alliance_name=character.alliance.name,
|
alliance_name=character.alliance.name,
|
||||||
|
alliance_ticker=getattr(character.alliance, 'ticker', None),
|
||||||
)
|
)
|
||||||
|
|
||||||
def update_character(self, character_id):
|
def update_character(self, character_id):
|
||||||
|
|||||||
23
allianceauth/eveonline/migrations/0010_alliance_ticker.py
Normal file
23
allianceauth/eveonline/migrations/0010_alliance_ticker.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 2.0.4 on 2018-04-17 20:07
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('eveonline', '0009_on_delete'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='evecharacter',
|
||||||
|
name='alliance_ticker',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=5, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='evecharacter',
|
||||||
|
name='corporation_ticker',
|
||||||
|
field=models.CharField(max_length=5),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -35,6 +35,15 @@ class EveAllianceInfo(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.alliance_name
|
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):
|
class EveCorporationInfo(models.Model):
|
||||||
corporation_id = models.CharField(max_length=254, unique=True)
|
corporation_id = models.CharField(max_length=254, unique=True)
|
||||||
@@ -60,15 +69,25 @@ class EveCorporationInfo(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.corporation_name
|
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):
|
class EveCharacter(models.Model):
|
||||||
character_id = models.CharField(max_length=254, unique=True)
|
character_id = models.CharField(max_length=254, unique=True)
|
||||||
character_name = models.CharField(max_length=254, unique=True)
|
character_name = models.CharField(max_length=254, unique=True)
|
||||||
corporation_id = models.CharField(max_length=254)
|
corporation_id = models.CharField(max_length=254)
|
||||||
corporation_name = models.CharField(max_length=254)
|
corporation_name = models.CharField(max_length=254)
|
||||||
corporation_ticker = models.CharField(max_length=254)
|
corporation_ticker = models.CharField(max_length=5)
|
||||||
alliance_id = models.CharField(max_length=254, blank=True, null=True, default='')
|
alliance_id = models.CharField(max_length=254, blank=True, null=True, default='')
|
||||||
alliance_name = models.CharField(max_length=254, blank=True, null=True, default='')
|
alliance_name = models.CharField(max_length=254, blank=True, null=True, default='')
|
||||||
|
alliance_ticker = models.CharField(max_length=5, blank=True, null=True, default='')
|
||||||
|
|
||||||
objects = EveCharacterManager()
|
objects = EveCharacterManager()
|
||||||
provider = EveCharacterProviderManager()
|
provider = EveCharacterProviderManager()
|
||||||
@@ -102,8 +121,18 @@ class EveCharacter(models.Model):
|
|||||||
self.corporation_ticker = character.corp.ticker
|
self.corporation_ticker = character.corp.ticker
|
||||||
self.alliance_id = character.alliance.id
|
self.alliance_id = character.alliance.id
|
||||||
self.alliance_name = character.alliance.name
|
self.alliance_name = character.alliance.name
|
||||||
|
self.alliance_ticker = getattr(character.alliance, 'ticker', None)
|
||||||
self.save()
|
self.save()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.character_name
|
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)
|
||||||
|
|||||||
@@ -81,7 +81,9 @@ class Alliance(Entity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def executor_corp(self):
|
def executor_corp(self):
|
||||||
return self.corp(self.executor_corp_id)
|
if self.executor_corp_id:
|
||||||
|
return self.corp(self.executor_corp_id)
|
||||||
|
return Entity(None, None)
|
||||||
|
|
||||||
|
|
||||||
class Character(Entity):
|
class Character(Entity):
|
||||||
@@ -150,10 +152,10 @@ class EveSwaggerProvider(EveProvider):
|
|||||||
corps = self.client.Alliance.get_alliances_alliance_id_corporations(alliance_id=alliance_id).result()
|
corps = self.client.Alliance.get_alliances_alliance_id_corporations(alliance_id=alliance_id).result()
|
||||||
model = Alliance(
|
model = Alliance(
|
||||||
id=alliance_id,
|
id=alliance_id,
|
||||||
name=data['alliance_name'],
|
name=data['name'],
|
||||||
ticker=data['ticker'],
|
ticker=data['ticker'],
|
||||||
corp_ids=corps,
|
corp_ids=corps,
|
||||||
executor_corp_id=data['executor_corp'],
|
executor_corp_id=data['executor_corporation_id'] if 'executor_corporation_id' in data else None,
|
||||||
)
|
)
|
||||||
return model
|
return model
|
||||||
except HTTPNotFound:
|
except HTTPNotFound:
|
||||||
@@ -164,7 +166,7 @@ class EveSwaggerProvider(EveProvider):
|
|||||||
data = self.client.Corporation.get_corporations_corporation_id(corporation_id=corp_id).result()
|
data = self.client.Corporation.get_corporations_corporation_id(corporation_id=corp_id).result()
|
||||||
model = Corporation(
|
model = Corporation(
|
||||||
id=corp_id,
|
id=corp_id,
|
||||||
name=data['corporation_name'],
|
name=data['name'],
|
||||||
ticker=data['ticker'],
|
ticker=data['ticker'],
|
||||||
ceo_id=data['ceo_id'],
|
ceo_id=data['ceo_id'],
|
||||||
members=data['member_count'],
|
members=data['member_count'],
|
||||||
@@ -177,12 +179,11 @@ class EveSwaggerProvider(EveProvider):
|
|||||||
def get_character(self, character_id):
|
def get_character(self, character_id):
|
||||||
try:
|
try:
|
||||||
data = self.client.Character.get_characters_character_id(character_id=character_id).result()
|
data = self.client.Character.get_characters_character_id(character_id=character_id).result()
|
||||||
alliance_id = self.adapter.get_corp(data['corporation_id']).alliance_id
|
|
||||||
model = Character(
|
model = Character(
|
||||||
id=character_id,
|
id=character_id,
|
||||||
name=data['name'],
|
name=data['name'],
|
||||||
corp_id=data['corporation_id'],
|
corp_id=data['corporation_id'],
|
||||||
alliance_id=alliance_id,
|
alliance_id=data['alliance_id'] if 'alliance_id' in data else None,
|
||||||
)
|
)
|
||||||
return model
|
return model
|
||||||
except (HTTPNotFound, HTTPUnprocessableEntity):
|
except (HTTPNotFound, HTTPUnprocessableEntity):
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -2,11 +2,9 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from allianceauth.optimer.models import OpTimer
|
|
||||||
|
|
||||||
|
|
||||||
class FatlinkForm(forms.Form):
|
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,
|
duration = forms.IntegerField(label=_("Duration of fat-link"), required=True, initial=30, min_value=1,
|
||||||
max_value=2147483647)
|
max_value=2147483647, help_text=_('minutes'))
|
||||||
fleet = forms.ModelChoiceField(label=_("Fleet"), queryset=OpTimer.objects.all().order_by('operation_name'))
|
|
||||||
|
|||||||
@@ -2,12 +2,10 @@
|
|||||||
# Generated by Django 1.10.1 on 2016-09-05 21:39
|
# Generated by Django 1.10.1 on 2016-09-05 21:39
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
from django.utils.timezone import utc
|
from django.utils import timezone
|
||||||
|
|
||||||
import allianceauth.fleetactivitytracking.models
|
import allianceauth.fleetactivitytracking.models
|
||||||
|
|
||||||
@@ -36,9 +34,9 @@ class Migration(migrations.Migration):
|
|||||||
name='Fatlink',
|
name='Fatlink',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('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()),
|
('duration', models.PositiveIntegerField()),
|
||||||
('fleet', models.CharField(default=b'', max_length=254)),
|
('fleet', models.CharField(default='', max_length=254)),
|
||||||
('name', models.CharField(max_length=254)),
|
('name', models.CharField(max_length=254)),
|
||||||
('hash', models.CharField(max_length=254, unique=True)),
|
('hash', models.CharField(max_length=254, unique=True)),
|
||||||
('creator', models.ForeignKey(on_delete=models.SET(
|
('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):
|
class Fatlink(models.Model):
|
||||||
fatdatetime = models.DateTimeField(default=timezone.now)
|
fatdatetime = models.DateTimeField(default=timezone.now)
|
||||||
duration = models.PositiveIntegerField()
|
duration = models.PositiveIntegerField()
|
||||||
fleet = models.CharField(max_length=254, default="")
|
fleet = models.CharField(max_length=254)
|
||||||
name = models.CharField(max_length=254)
|
|
||||||
hash = models.CharField(max_length=254, unique=True)
|
hash = models.CharField(max_length=254, unique=True)
|
||||||
creator = models.ForeignKey(User, on_delete=models.SET(get_sentinel_user))
|
creator = models.ForeignKey(User, on_delete=models.SET(get_sentinel_user))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.fleet
|
||||||
|
|
||||||
|
|
||||||
class Fat(models.Model):
|
class Fat(models.Model):
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -16,7 +16,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-lg-10 col-sm-2">
|
<div class="col-lg-10 col-sm-2">
|
||||||
<div class="alert alert-danger" role="alert">{% trans "Character not registered!" %}</div>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,12 +10,12 @@
|
|||||||
<h1 class="page-header text-center">{% blocktrans %}Participation data statistics for {{ month }}, {{ year }}{% endblocktrans %}
|
<h1 class="page-header text-center">{% blocktrans %}Participation data statistics for {{ month }}, {{ year }}{% endblocktrans %}
|
||||||
{% if char_id %}
|
{% if char_id %}
|
||||||
<div class="text-right">
|
<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 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 next_month|date:'Y' next_month|date:'m' %}" class="btn btn-info">{% trans "Next month" %}</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h1>
|
</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">
|
<table class="table table-responsive">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="col-md-2 text-center">{% trans "Ship" %}</th>
|
<th class="col-md-2 text-center">{% trans "Ship" %}</th>
|
||||||
@@ -29,26 +29,24 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
{% if created_fats %}
|
{% 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 %}
|
{% if created_fats %}
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<tr>
|
<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 "Fleet" %}</th>
|
||||||
|
<th class="text-center">{% trans "Creator" %}</th>
|
||||||
<th class="text-center">{% trans "Eve Time" %}</th>
|
<th class="text-center">{% trans "Eve Time" %}</th>
|
||||||
<th class="text-center">{% trans "Duration" %}</th>
|
<th class="text-center">{% trans "Duration" %}</th>
|
||||||
<th class="text-center">{% trans "Edit" %}</th>
|
<th class="text-center">{% trans "Edit" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% for link in created_fats %}
|
{% for link in created_fats %}
|
||||||
<tr>
|
<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.creator.username }}</td>
|
||||||
<td class="text-center">{{ link.fleet }}</td>
|
|
||||||
<td class="text-center">{{ link.fatdatetime }}</td>
|
<td class="text-center">{{ link.fatdatetime }}</td>
|
||||||
<td class="text-center">{{ link.duration }}</td>
|
<td class="text-center">{{ link.duration }}</td>
|
||||||
<td class="text-center">
|
<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
|
<button type="button" class="btn btn-info"><span
|
||||||
class="glyphicon glyphicon-edit"></span></button>
|
class="glyphicon glyphicon-edit"></span></button>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
{% if fats %}
|
{% if fats %}
|
||||||
<table class="table table-responsive">
|
<table class="table table-responsive">
|
||||||
<tr>
|
<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 "Character" %}</th>
|
||||||
<th class="text-center">{% trans "System" %}</th>
|
<th class="text-center">{% trans "System" %}</th>
|
||||||
<th class="text-center">{% trans "Ship" %}</th>
|
<th class="text-center">{% trans "Ship" %}</th>
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{% for fat in fats %}
|
{% for fat in fats %}
|
||||||
<tr>
|
<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>
|
<td class="text-center">{{ fat.character.character_name }}</td>
|
||||||
{% if fat.station != "No Station" %}
|
{% if fat.station != "No Station" %}
|
||||||
<td class="text-center">{% blocktrans %}Docked in {% endblocktrans %}{{ fat.system }}</td>
|
<td class="text-center">{% blocktrans %}Docked in {% endblocktrans %}{{ fat.system }}</td>
|
||||||
@@ -79,13 +79,13 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{% for link in fatlinks %}
|
{% for link in fatlinks %}
|
||||||
<tr>
|
<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.creator.username }}</td>
|
||||||
<td class="text-center">{{ link.fleet }}</td>
|
<td class="text-center">{{ link.fleet }}</td>
|
||||||
<td class="text-center">{{ link.fatdatetime }}</td>
|
<td class="text-center">{{ link.fatdatetime }}</td>
|
||||||
<td class="text-center">{{ link.duration }}</td>
|
<td class="text-center">{{ link.duration }}</td>
|
||||||
<td class="text-center">
|
<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>
|
<span class="glyphicon glyphicon-edit"></span>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -25,10 +25,6 @@ urlpatterns = [
|
|||||||
views.fatlink_monthly_personal_statistics_view,
|
views.fatlink_monthly_personal_statistics_view,
|
||||||
name='user_statistics_month'),
|
name='user_statistics_month'),
|
||||||
url(r'^create/$', views.create_fatlink_view, name='create'),
|
url(r'^create/$', views.create_fatlink_view, name='create'),
|
||||||
url(r'^modify/$', views.modify_fatlink_view, name='modify'),
|
url(r'^modify/(?P<fat_hash>[a-zA-Z0-9_-]+)/$', views.modify_fatlink_view, name='modify'),
|
||||||
url(r'^modify/(?P<hash>[a-zA-Z0-9_-]+)/([a-z0-9_-]+)$',
|
url(r'^link/(?P<fat_hash>[a-zA-Z0-9]+)/$', views.click_fatlink_view, name='click'),
|
||||||
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),
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import random
|
|
||||||
import string
|
|
||||||
|
|
||||||
from allianceauth.authentication.models import CharacterOwnership
|
from allianceauth.authentication.models import CharacterOwnership
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
@@ -17,7 +15,7 @@ from esi.decorators import token_required
|
|||||||
from allianceauth.eveonline.providers import provider
|
from allianceauth.eveonline.providers import provider
|
||||||
from .forms import FatlinkForm
|
from .forms import FatlinkForm
|
||||||
from .models import Fatlink, Fat
|
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 EveAllianceInfo
|
||||||
from allianceauth.eveonline.models import EveCharacter
|
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')
|
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:
|
for fat in personal_fats:
|
||||||
fatdate = fat.fatlink.fatdatetime
|
fatdate = fat.fatlink.fatdatetime
|
||||||
@@ -236,8 +234,8 @@ def fatlink_monthly_personal_statistics_view(request, year, month, char_id=None)
|
|||||||
@login_required
|
@login_required
|
||||||
@token_required(
|
@token_required(
|
||||||
scopes=['esi-location.read_location.v1', 'esi-location.read_ship_type.v1', 'esi-universe.read_structures.v1'])
|
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):
|
def click_fatlink_view(request, token, fat_hash=None):
|
||||||
fatlink = get_object_or_404(Fatlink, hash=hash, name=fatname)
|
fatlink = get_object_or_404(Fatlink, hash=fat_hash)
|
||||||
|
|
||||||
if (timezone.now() - fatlink.fatdatetime) < datetime.timedelta(seconds=(fatlink.duration * 60)):
|
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)
|
logger.debug("Submitting fleetactivitytracking by user %s" % request.user)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
fatlink = Fatlink()
|
fatlink = Fatlink()
|
||||||
fatlink.name = slugify(form.cleaned_data["fatname"])
|
|
||||||
fatlink.fleet = form.cleaned_data["fleet"]
|
fatlink.fleet = form.cleaned_data["fleet"]
|
||||||
fatlink.duration = form.cleaned_data["duration"]
|
fatlink.duration = form.cleaned_data["duration"]
|
||||||
fatlink.fatdatetime = timezone.now()
|
fatlink.fatdatetime = timezone.now()
|
||||||
fatlink.creator = request.user
|
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:
|
try:
|
||||||
fatlink.full_clean()
|
fatlink.full_clean()
|
||||||
fatlink.save()
|
fatlink.save()
|
||||||
@@ -331,25 +328,19 @@ def create_fatlink_view(request):
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('auth.fleetactivitytracking')
|
@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)
|
logger.debug("modify_fatlink_view called by user %s" % request.user)
|
||||||
if not hash:
|
fatlink = get_object_or_404(Fatlink, hash=fat_hash)
|
||||||
return redirect('fatlink:view')
|
|
||||||
|
|
||||||
try:
|
|
||||||
fatlink = Fatlink.objects.get(hash=hash)
|
|
||||||
except Fatlink.DoesNotExist:
|
|
||||||
raise Http404
|
|
||||||
|
|
||||||
if request.GET.get('removechar', None):
|
if request.GET.get('removechar', None):
|
||||||
character_id = request.GET.get('removechar')
|
character_id = request.GET.get('removechar')
|
||||||
character = EveCharacter.objects.get(character_id=character_id)
|
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()
|
Fat.objects.filter(fatlink=fatlink).filter(character=character).delete()
|
||||||
|
|
||||||
if request.GET.get('deletefat', None):
|
if request.GET.get('deletefat', None):
|
||||||
logger.debug("Removing fleetactivitytracking %s" % fatlink.name)
|
logger.debug("Removing fleetactivitytracking %s" % fatlink)
|
||||||
fatlink.delete()
|
fatlink.delete()
|
||||||
return redirect('fatlink:view')
|
return redirect('fatlink:view')
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +1,68 @@
|
|||||||
from django.contrib import admin
|
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 AuthGroup
|
||||||
from .models import GroupRequest
|
from .models import GroupRequest
|
||||||
|
|
||||||
|
|
||||||
class AuthGroupAdmin(admin.ModelAdmin):
|
class AuthGroupInlineAdmin(admin.StackedInline):
|
||||||
"""
|
model = AuthGroup
|
||||||
Admin model for AuthGroup
|
|
||||||
"""
|
|
||||||
filter_horizontal = ('group_leaders',)
|
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:
|
class Meta:
|
||||||
proxy = True
|
proxy = True
|
||||||
verbose_name = Group._meta.verbose_name
|
verbose_name = BaseGroup._meta.verbose_name
|
||||||
verbose_name_plural = Group._meta.verbose_name_plural
|
verbose_name_plural = BaseGroup._meta.verbose_name_plural
|
||||||
|
|
||||||
try:
|
try:
|
||||||
admin.site.unregister(Group)
|
admin.site.unregister(BaseGroup)
|
||||||
finally:
|
finally:
|
||||||
admin.site.register(ProxyGroup)
|
admin.site.register(Group, GroupAdmin)
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(GroupRequest)
|
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):
|
class GroupManagementConfig(AppConfig):
|
||||||
name = 'allianceauth.groupmanagement'
|
name = 'allianceauth.groupmanagement'
|
||||||
label = 'groupmanagement'
|
label = 'groupmanagement'
|
||||||
|
verbose_name = 'Group Management'
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class Migration(migrations.Migration):
|
|||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='ProxyGroup',
|
name='Group',
|
||||||
fields=[
|
fields=[
|
||||||
],
|
],
|
||||||
options={
|
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 = (
|
permissions = (
|
||||||
("request_groups", u"Can request non-public groups"),
|
("request_groups", u"Can request non-public groups"),
|
||||||
)
|
)
|
||||||
|
default_permissions = tuple()
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Group)
|
@receiver(post_save, sender=Group)
|
||||||
|
|||||||
@@ -154,6 +154,12 @@
|
|||||||
class="btn btn-primary">
|
class="btn btn-primary">
|
||||||
<span class="glyphicon glyphicon-eye-open"></span>
|
<span class="glyphicon glyphicon-eye-open"></span>
|
||||||
</a>
|
</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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -13,19 +13,19 @@ urlpatterns = [
|
|||||||
name="create_view"),
|
name="create_view"),
|
||||||
url(r'^remove/(\w+)', views.hr_application_remove,
|
url(r'^remove/(\w+)', views.hr_application_remove,
|
||||||
name="remove"),
|
name="remove"),
|
||||||
url(r'view/(\w+)', views.hr_application_view,
|
url(r'^view/(\w+)', views.hr_application_view,
|
||||||
name="view"),
|
name="view"),
|
||||||
url(r'personal/view/(\w+)', views.hr_application_personal_view,
|
url(r'^personal/view/(\w+)', views.hr_application_personal_view,
|
||||||
name="personal_view"),
|
name="personal_view"),
|
||||||
url(r'personal/removal/(\w+)',
|
url(r'^personal/removal/(\w+)',
|
||||||
views.hr_application_personal_removal,
|
views.hr_application_personal_removal,
|
||||||
name="personal_removal"),
|
name="personal_removal"),
|
||||||
url(r'approve/(\w+)', views.hr_application_approve,
|
url(r'^approve/(\w+)', views.hr_application_approve,
|
||||||
name="approve"),
|
name="approve"),
|
||||||
url(r'reject/(\w+)', views.hr_application_reject,
|
url(r'^reject/(\w+)', views.hr_application_reject,
|
||||||
name="reject"),
|
name="reject"),
|
||||||
url(r'search/', views.hr_application_search,
|
url(r'^search/', views.hr_application_search,
|
||||||
name="search"),
|
name="search"),
|
||||||
url(r'mark_in_progress/(\w+)', views.hr_application_mark_in_progress,
|
url(r'^mark_in_progress/(\w+)', views.hr_application_mark_in_progress,
|
||||||
name="mark_in_progress"),
|
name="mark_in_progress"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ def hr_application_create_view(request, form_id=None):
|
|||||||
""))
|
""))
|
||||||
response.save()
|
response.save()
|
||||||
logger.info("%s created %s" % (request.user, application))
|
logger.info("%s created %s" % (request.user, application))
|
||||||
return redirect('hrapplications:view')
|
return redirect('hrapplications:personal_view', application.id)
|
||||||
else:
|
else:
|
||||||
questions = app_form.questions.all()
|
questions = app_form.questions.all()
|
||||||
return render(request, 'hrapplications/create.html',
|
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)
|
return render(request, 'hrapplications/view.html', context=context)
|
||||||
else:
|
else:
|
||||||
logger.warn("User %s not authorized to view %s" % (request.user, app))
|
logger.warn("User %s not authorized to view %s" % (request.user, app))
|
||||||
return redirect('hrapplications:view')
|
return redirect('hrapplications:personal_view')
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@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))
|
logger.warn("User %s attempting to delete reviewed app %s" % (request.user, app))
|
||||||
else:
|
else:
|
||||||
logger.warn("User %s not authorized to delete %s" % (request.user, app))
|
logger.warn("User %s not authorized to delete %s" % (request.user, app))
|
||||||
return redirect('hrapplications:view')
|
return redirect('hrapplications:index')
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@@ -158,7 +158,7 @@ def hr_application_remove(request, app_id):
|
|||||||
logger.info("User %s deleting %s" % (request.user, app))
|
logger.info("User %s deleting %s" % (request.user, app))
|
||||||
app.delete()
|
app.delete()
|
||||||
notify(app.user, "Application Deleted", message="Your application to %s was deleted." % app.form.corp)
|
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
|
@login_required
|
||||||
@@ -175,7 +175,7 @@ def hr_application_approve(request, app_id):
|
|||||||
level="success")
|
level="success")
|
||||||
else:
|
else:
|
||||||
logger.warn("User %s not authorized to approve %s" % (request.user, app))
|
logger.warn("User %s not authorized to approve %s" % (request.user, app))
|
||||||
return redirect('hrapplications:view')
|
return redirect('hrapplications:index')
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@@ -192,7 +192,7 @@ def hr_application_reject(request, app_id):
|
|||||||
level="danger")
|
level="danger")
|
||||||
else:
|
else:
|
||||||
logger.warn("User %s not authorized to reject %s" % (request.user, app))
|
logger.warn("User %s not authorized to reject %s" % (request.user, app))
|
||||||
return redirect('hrapplications:view')
|
return redirect('hrapplications:index')
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from allianceauth.tests.auth_utils import AuthUtils
|
|||||||
class PermissionsToolViewsTestCase(WebTest):
|
class PermissionsToolViewsTestCase(WebTest):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.member = AuthUtils.create_member('auth_member')
|
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.email = 'auth_member@example.com'
|
||||||
self.member.save()
|
self.member.save()
|
||||||
self.none_user = AuthUtils.create_user('none_user', disconnect_signals=True)
|
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,11 @@ app = Celery('{{ project_name }}')
|
|||||||
|
|
||||||
# Using a string here means the worker don't have to serialize
|
# Using a string here means the worker don't have to serialize
|
||||||
# the configuration object to child processes.
|
# the configuration object to child processes.
|
||||||
app.config_from_object('django.conf:settings', namespace='CELERY')
|
app.config_from_object('django.conf:settings')
|
||||||
|
app.conf.ONCE = {
|
||||||
|
'backend': 'allianceauth.services.tasks.DjangoBackend',
|
||||||
|
'settings': {}
|
||||||
|
}
|
||||||
|
|
||||||
# Load task modules from all registered Django app configs.
|
# Load task modules from all registered Django app configs.
|
||||||
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
|
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
# -*- coding: UTF-8 -*-
|
# -*- coding: UTF-8 -*-
|
||||||
"""
|
"""
|
||||||
Django settings for alliance_auth project.
|
DO NOT EDIT THIS FILE
|
||||||
|
|
||||||
Generated by 'django-admin startproject' using Django 1.10.1.
|
This settings file contains everything needed for Alliance Auth projects to function.
|
||||||
|
It gets overwritten by the 'allianceauth update' command.
|
||||||
For more information on this file, see
|
If you wish to make changes, overload the setting in your project's settings file (local.py).
|
||||||
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/
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@@ -17,7 +13,6 @@ from django.contrib import messages
|
|||||||
from celery.schedules import crontab
|
from celery.schedules import crontab
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
# Core apps - required to function
|
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
@@ -38,17 +33,19 @@ INSTALLED_APPS = [
|
|||||||
'allianceauth.thirdparty.navhelper',
|
'allianceauth.thirdparty.navhelper',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
SECRET_KEY = "wow I'm a really bad default secret key"
|
||||||
|
|
||||||
# Celery configuration
|
# Celery configuration
|
||||||
CELERY_BROKER_URL = 'redis://localhost:6379/0'
|
BROKER_URL = 'redis://localhost:6379/0'
|
||||||
CELERYBEAT_SCHEDULER = "django_celery_beat.schedulers.DatabaseScheduler"
|
CELERYBEAT_SCHEDULER = "django_celery_beat.schedulers.DatabaseScheduler"
|
||||||
CELERYBEAT_SCHEDULE = {
|
CELERYBEAT_SCHEDULE = {
|
||||||
'esi_cleanup_callbackredirect': {
|
'esi_cleanup_callbackredirect': {
|
||||||
'task': 'esi.tasks.cleanup_callbackredirect',
|
'task': 'esi.tasks.cleanup_callbackredirect',
|
||||||
'schedule': crontab(hour='*/4'),
|
'schedule': crontab(minute=0, hour='*/4'),
|
||||||
},
|
},
|
||||||
'esi_cleanup_token': {
|
'esi_cleanup_token': {
|
||||||
'task': 'esi.tasks.cleanup_token',
|
'task': 'esi.tasks.cleanup_token',
|
||||||
'schedule': crontab(day_of_month='*/1'),
|
'schedule': crontab(minute=0, hour=0),
|
||||||
},
|
},
|
||||||
'run_model_update': {
|
'run_model_update': {
|
||||||
'task': 'allianceauth.eveonline.tasks.run_model_update',
|
'task': 'allianceauth.eveonline.tasks.run_model_update',
|
||||||
@@ -56,7 +53,7 @@ CELERYBEAT_SCHEDULE = {
|
|||||||
},
|
},
|
||||||
'check_all_character_ownership': {
|
'check_all_character_ownership': {
|
||||||
'task': 'allianceauth.authentication.tasks.check_all_character_ownership',
|
'task': 'allianceauth.authentication.tasks.check_all_character_ownership',
|
||||||
'schedule': crontab(hour='*/4'),
|
'schedule': crontab(minute=0, hour='*/4'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +87,7 @@ LANGUAGES = (
|
|||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
'DIRS': [],
|
'DIRS': [os.path.join(PROJECT_DIR, 'templates')],
|
||||||
'APP_DIRS': True,
|
'APP_DIRS': True,
|
||||||
'OPTIONS': {
|
'OPTIONS': {
|
||||||
'context_processors': [
|
'context_processors': [
|
||||||
@@ -148,8 +145,11 @@ USE_TZ = True
|
|||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
# https://docs.djangoproject.com/en/1.10/howto/static-files/
|
# https://docs.djangoproject.com/en/1.10/howto/static-files/
|
||||||
|
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = '/static/'
|
||||||
|
STATICFILES_DIRS = [
|
||||||
|
os.path.join(PROJECT_DIR, 'static'),
|
||||||
|
]
|
||||||
|
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
|
||||||
|
|
||||||
# Bootstrap messaging css workaround
|
# Bootstrap messaging css workaround
|
||||||
MESSAGE_TAGS = {
|
MESSAGE_TAGS = {
|
||||||
@@ -166,7 +166,6 @@ CACHES = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SECRET_KEY = 'this is a very bad secret key you should change'
|
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
ALLOWED_HOSTS = ['*']
|
ALLOWED_HOSTS = ['*']
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
@@ -178,40 +177,22 @@ DATABASES = {
|
|||||||
|
|
||||||
SITE_NAME = 'Alliance Auth'
|
SITE_NAME = 'Alliance Auth'
|
||||||
|
|
||||||
#################
|
LOGIN_URL = 'auth_login_user' # view that handles login logic
|
||||||
# Login Settings
|
|
||||||
#################
|
LOGIN_REDIRECT_URL = 'authentication:dashboard' # default destination when logging in if no redirect specified
|
||||||
# LOGIN_REDIRECT_URL - default destination when logging in if no redirect specified
|
LOGOUT_REDIRECT_URL = 'authentication:dashboard' # destination after logging out
|
||||||
# LOGOUT_REDIRECT_URL - destination after logging out
|
|
||||||
# Both of these redirects accept values as per the django redirect shortcut
|
# Both of these redirects accept values as per the django redirect shortcut
|
||||||
# https://docs.djangoproject.com/en/1.11/topics/http/shortcuts/#redirect
|
# https://docs.djangoproject.com/en/1.11/topics/http/shortcuts/#redirect
|
||||||
# - url names eg 'authentication:dashboard'
|
# - url names eg 'authentication:dashboard'
|
||||||
# - relative urls eg '/dashboard'
|
# - relative urls eg '/dashboard'
|
||||||
# - absolute urls eg 'http://example.com/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
|
# scopes required on new tokens when logging in. Cannot be blank.
|
||||||
##################
|
LOGIN_TOKEN_SCOPES = ['publicData']
|
||||||
LOGIN_URL = 'auth_login_user'
|
|
||||||
LOGIN_REDIRECT_URL = 'authentication:dashboard'
|
# number of days email verification links are valid for
|
||||||
LOGOUT_REDIRECT_URL = 'authentication:dashboard'
|
|
||||||
LOGIN_TOKEN_SCOPES = ['esi-characters.read_opportunities.v1']
|
|
||||||
ACCOUNT_ACTIVATION_DAYS = 1
|
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 = {
|
LOGGING = {
|
||||||
'version': 1,
|
'version': 1,
|
||||||
'disable_existing_loggers': False,
|
'disable_existing_loggers': False,
|
||||||
@@ -253,5 +234,9 @@ LOGGING = {
|
|||||||
'handlers': ['log_file', 'console'],
|
'handlers': ['log_file', 'console'],
|
||||||
'level': 'ERROR',
|
'level': 'ERROR',
|
||||||
},
|
},
|
||||||
|
'esi': {
|
||||||
|
'handlers': ['log_file', 'console'],
|
||||||
|
'level': 'DEBUG',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,50 +1,28 @@
|
|||||||
|
# Every setting in base.py can be overloaded by redefining it here.
|
||||||
from .base import *
|
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'
|
ROOT_URLCONF = '{{ project_name }}.urls'
|
||||||
WSGI_APPLICATION = '{{ project_name }}.wsgi.application'
|
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 }}'
|
SECRET_KEY = '{{ secret_key }}'
|
||||||
|
|
||||||
#######################################
|
# This is where css/images will be placed for your webserver to read
|
||||||
# Site Name #
|
STATIC_ROOT = "/var/www/{{ project_name }}/static/"
|
||||||
#######################################
|
|
||||||
# Change this to change the name of the
|
# Change this to change the name of the auth site displayed
|
||||||
# auth site displayed in page titles
|
# in page titles and the site header.
|
||||||
# and the site header.
|
|
||||||
#######################################
|
|
||||||
SITE_NAME = '{{ project_name }}'
|
SITE_NAME = '{{ project_name }}'
|
||||||
|
|
||||||
#######################################
|
# Change this to enable/disable debug mode, which displays
|
||||||
# Debug Mode #
|
# 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
|
DEBUG = False
|
||||||
|
|
||||||
#######################################
|
|
||||||
# Additional Applications #
|
|
||||||
#######################################
|
|
||||||
# Add any additional apps to this list.
|
# Add any additional apps to this list.
|
||||||
#######################################
|
|
||||||
INSTALLED_APPS += [
|
INSTALLED_APPS += [
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
#######################################
|
# Enter credentials to use MySQL/MariaDB. Comment out to use sqlite3
|
||||||
# Database Settings #
|
|
||||||
#######################################
|
|
||||||
# Uncomment and change the database name
|
|
||||||
# and credentials to use MySQL/MariaDB.
|
|
||||||
# Leave commented to use sqlite3
|
|
||||||
#######################################
|
|
||||||
"""
|
|
||||||
DATABASES['default'] = {
|
DATABASES['default'] = {
|
||||||
'ENGINE': 'django.db.backends.mysql',
|
'ENGINE': 'django.db.backends.mysql',
|
||||||
'NAME': 'alliance_auth',
|
'NAME': 'alliance_auth',
|
||||||
@@ -53,33 +31,23 @@ DATABASES['default'] = {
|
|||||||
'HOST': '127.0.0.1',
|
'HOST': '127.0.0.1',
|
||||||
'PORT': '3306',
|
'PORT': '3306',
|
||||||
}
|
}
|
||||||
"""
|
|
||||||
|
|
||||||
#######################################
|
# Register an application at https://developers.eveonline.com for Authentication
|
||||||
# SSO Settings #
|
# & 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
|
||||||
# Register an application at
|
# Logging in to auth requires the publicData scope (can be overridden through the
|
||||||
# https://developers.eveonline.com
|
# LOGIN_TOKEN_SCOPES setting). Other apps may require more (see their docs).
|
||||||
# and fill out these settings.
|
|
||||||
# Be sure to set the callback URL to
|
|
||||||
# https://example.com/sso/callback
|
|
||||||
# substituting your domain for example.com
|
|
||||||
#######################################
|
|
||||||
ESI_SSO_CLIENT_ID = ''
|
ESI_SSO_CLIENT_ID = ''
|
||||||
ESI_SSO_CLIENT_SECRET = ''
|
ESI_SSO_CLIENT_SECRET = ''
|
||||||
ESI_SSO_CALLBACK_URL = ''
|
ESI_SSO_CALLBACK_URL = ''
|
||||||
|
|
||||||
#######################################
|
# By default emails are validated before new users can log in.
|
||||||
# Email Settings #
|
# It's recommended to use a free service like SparkPost or Elastic Email to send email.
|
||||||
#######################################
|
|
||||||
# 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.
|
|
||||||
# https://www.sparkpost.com/docs/integrations/django/
|
# https://www.sparkpost.com/docs/integrations/django/
|
||||||
# Set the default from email to something
|
# https://elasticemail.com/resources/settings/smtp-api/
|
||||||
# like 'noreply@example.com'
|
# Set the default from email to something like 'noreply@example.com'
|
||||||
#######################################
|
# Email validation can be turned off by uncommenting the line below. This can break some services.
|
||||||
|
# REGISTRATION_VERIFY_EMAIL = False
|
||||||
EMAIL_HOST = ''
|
EMAIL_HOST = ''
|
||||||
EMAIL_PORT = 587
|
EMAIL_PORT = 587
|
||||||
EMAIL_HOST_USER = ''
|
EMAIL_HOST_USER = ''
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ class NameFormatConfigForm(forms.ModelForm):
|
|||||||
|
|
||||||
class NameFormatConfigAdmin(admin.ModelAdmin):
|
class NameFormatConfigAdmin(admin.ModelAdmin):
|
||||||
form = NameFormatConfigForm
|
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)
|
admin.site.register(NameFormatConfig, NameFormatConfigAdmin)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
from django.conf.urls import include, url
|
from django.conf.urls import include, url
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from string import Formatter
|
from string import Formatter
|
||||||
|
|
||||||
@@ -160,15 +159,12 @@ class NameFormatter:
|
|||||||
'corp_id': getattr(main_char, 'corporation_id', None),
|
'corp_id': getattr(main_char, 'corporation_id', None),
|
||||||
'alliance_name': getattr(main_char, 'alliance_name', None),
|
'alliance_name': getattr(main_char, 'alliance_name', None),
|
||||||
'alliance_id': getattr(main_char, 'alliance_id', None),
|
'alliance_id': getattr(main_char, 'alliance_id', None),
|
||||||
|
'alliance_ticker': getattr(main_char, 'alliance_ticker', None),
|
||||||
'username': self.user.username,
|
'username': self.user.username,
|
||||||
}
|
}
|
||||||
|
|
||||||
if main_char is not None and 'alliance_ticker' in self.string_formatter:
|
format_data['alliance_or_corp_name'] = format_data['alliance_name'] or format_data['corp_name']
|
||||||
# Reduces db lookups
|
format_data['alliance_or_corp_ticker'] = format_data['alliance_ticker'] or format_data['corp_ticker']
|
||||||
try:
|
|
||||||
format_data['alliance_ticker'] = getattr(getattr(main_char, 'alliance', None), 'alliance_ticker', None)
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
format_data['alliance_ticker'] = None
|
|
||||||
return format_data
|
return format_data
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
|
|||||||
@@ -9,5 +9,5 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
for u in User.objects.all():
|
for u in User.objects.all():
|
||||||
validate_services(u)
|
validate_services(u.pk)
|
||||||
self.stdout.write(self.style.SUCCESS('Verified all user service accounts.'))
|
self.stdout.write(self.style.SUCCESS('Verified all user service accounts.'))
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ class Migration(migrations.Migration):
|
|||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('services', '0001_squashed_0003_delete_groupcache'),
|
('services', '0001_squashed_0003_delete_groupcache'),
|
||||||
|
('authentication', '0015_user_profiles'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import requests
|
import requests
|
||||||
import json
|
|
||||||
import re
|
|
||||||
import math
|
import math
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from requests_oauthlib import OAuth2Session
|
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.
|
Users have reported weird unauthorized errors we don't understand. So now we ask for full server admin.
|
||||||
It's almost fixed the problem.
|
It's almost fixed the problem.
|
||||||
"""
|
"""
|
||||||
# kick members, manage roles, manage nicknames
|
# kick members, manage roles, manage nicknames, create instant invite
|
||||||
# BOT_PERMISSIONS = 0x00000002 + 0x10000000 + 0x08000000
|
# BOT_PERMISSIONS = 0x00000002 + 0x10000000 + 0x08000000 + 0x00000001
|
||||||
BOT_PERMISSIONS = 0x00000008
|
BOT_PERMISSIONS = 0x00000008
|
||||||
|
|
||||||
# get user ID, accept invite
|
# get user ID, accept invite
|
||||||
@@ -34,7 +32,7 @@ SCOPES = [
|
|||||||
'guilds.join',
|
'guilds.join',
|
||||||
]
|
]
|
||||||
|
|
||||||
GROUP_CACHE_MAX_AGE = int(getattr(settings, 'DISCORD_GROUP_CACHE_MAX_AGE', 2 * 60 * 60)) # 2 hours default
|
GROUP_CACHE_MAX_AGE = getattr(settings, 'DISCORD_GROUP_CACHE_MAX_AGE', 2 * 60 * 60) # 2 hours default
|
||||||
|
|
||||||
|
|
||||||
class DiscordApiException(Exception):
|
class DiscordApiException(Exception):
|
||||||
@@ -109,7 +107,7 @@ def api_backoff(func):
|
|||||||
backoff_timer = datetime.datetime.strptime(existing_backoff, cache_time_format)
|
backoff_timer = datetime.datetime.strptime(existing_backoff, cache_time_format)
|
||||||
if backoff_timer > datetime.datetime.utcnow():
|
if backoff_timer > datetime.datetime.utcnow():
|
||||||
backoff_seconds = (backoff_timer - datetime.datetime.utcnow()).total_seconds()
|
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
|
# Still under backoff
|
||||||
raise PerformBackoff(
|
raise PerformBackoff(
|
||||||
retry_after=backoff_seconds,
|
retry_after=backoff_seconds,
|
||||||
@@ -117,8 +115,7 @@ def api_backoff(func):
|
|||||||
global_ratelimit=bool(existing_global_backoff)
|
global_ratelimit=bool(existing_global_backoff)
|
||||||
)
|
)
|
||||||
logger.debug("Calling API calling function")
|
logger.debug("Calling API calling function")
|
||||||
func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
break
|
|
||||||
except requests.HTTPError as e:
|
except requests.HTTPError as e:
|
||||||
if e.response.status_code == 429:
|
if e.response.status_code == 429:
|
||||||
try:
|
try:
|
||||||
@@ -163,12 +160,11 @@ class DiscordOAuthManager:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _sanitize_name(name):
|
def _sanitize_name(name):
|
||||||
return re.sub('[^\w.-]', '', name)[:32]
|
return name[:32]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _sanitize_groupname(name):
|
def _sanitize_group_name(name):
|
||||||
name = name.strip(' _')
|
return name[:100]
|
||||||
return DiscordOAuthManager._sanitize_name(name)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_bot_add_url():
|
def generate_bot_add_url():
|
||||||
@@ -187,23 +183,33 @@ class DiscordOAuthManager:
|
|||||||
return token
|
return token
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def add_user(code):
|
def add_user(code, groups, nickname=None):
|
||||||
try:
|
try:
|
||||||
token = DiscordOAuthManager._process_callback_code(code)['access_token']
|
token = DiscordOAuthManager._process_callback_code(code)['access_token']
|
||||||
logger.debug("Received token from OAuth")
|
logger.debug("Received token from OAuth")
|
||||||
|
|
||||||
custom_headers = {'accept': 'application/json', 'authorization': 'Bearer ' + token}
|
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"
|
path = DISCORD_URL + "/users/@me"
|
||||||
r = requests.get(path, headers=custom_headers)
|
r = requests.get(path, headers=custom_headers)
|
||||||
logger.debug("Got status code %s after retrieving Discord profile" % r.status_code)
|
logger.debug("Got status code %s after retrieving Discord profile" % r.status_code)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
|
|
||||||
user_id = r.json()['id']
|
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'] = DiscordOAuthManager._sanitize_name(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)
|
logger.info("Added Discord user ID %s to server." % user_id)
|
||||||
return user_id
|
return user_id
|
||||||
except:
|
except:
|
||||||
@@ -211,6 +217,7 @@ class DiscordOAuthManager:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@api_backoff
|
||||||
def update_nickname(user_id, nickname):
|
def update_nickname(user_id, nickname):
|
||||||
try:
|
try:
|
||||||
nickname = DiscordOAuthManager._sanitize_name(nickname)
|
nickname = DiscordOAuthManager._sanitize_name(nickname)
|
||||||
@@ -260,7 +267,7 @@ class DiscordOAuthManager:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _group_name_to_id(name):
|
def _group_name_to_id(name):
|
||||||
name = DiscordOAuthManager._sanitize_groupname(name)
|
name = DiscordOAuthManager._sanitize_group_name(name)
|
||||||
|
|
||||||
def get_or_make_role():
|
def get_or_make_role():
|
||||||
groups = DiscordOAuthManager._get_groups()
|
groups = DiscordOAuthManager._get_groups()
|
||||||
@@ -271,42 +278,61 @@ class DiscordOAuthManager:
|
|||||||
return cache.get_or_set(DiscordOAuthManager._generate_cache_role_key(name), get_or_make_role, GROUP_CACHE_MAX_AGE)
|
return cache.get_or_set(DiscordOAuthManager._generate_cache_role_key(name), get_or_make_role, GROUP_CACHE_MAX_AGE)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __generate_role():
|
def __generate_role(name, **kwargs):
|
||||||
custom_headers = {'accept': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
custom_headers = {'accept': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
||||||
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/roles"
|
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)
|
logger.debug("Received status code %s after generating new role." % r.status_code)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
@staticmethod
|
@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}
|
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)
|
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))
|
logger.debug("Received status code %s after editing role id %s" % (r.status_code, role_id))
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _create_group(name):
|
def _create_group(name):
|
||||||
role = DiscordOAuthManager.__generate_role()
|
return DiscordOAuthManager.__generate_role(name)
|
||||||
return DiscordOAuthManager.__edit_role(role['id'], name)
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_user(user_id):
|
||||||
|
custom_headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
||||||
|
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/members/" + str(user_id)
|
||||||
|
r = requests.get(path, headers=custom_headers)
|
||||||
|
r.raise_for_status()
|
||||||
|
return r.json()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_user_roles(user_id):
|
||||||
|
user = DiscordOAuthManager._get_user(user_id)
|
||||||
|
return user['roles']
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _modify_user_role(user_id, role_id, method):
|
||||||
|
custom_headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
||||||
|
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/members/" + str(user_id) + "/roles/" + str(
|
||||||
|
role_id)
|
||||||
|
r = getattr(requests, method)(path, headers=custom_headers)
|
||||||
|
r.raise_for_status()
|
||||||
|
logger.debug("%s role %s for user %s" % (method, role_id, user_id))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@api_backoff
|
@api_backoff
|
||||||
def update_groups(user_id, groups):
|
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_group_name(g)) for g in groups]
|
||||||
group_ids = [DiscordOAuthManager._group_name_to_id(DiscordOAuthManager._sanitize_groupname(g)) for g in groups]
|
user_group_ids = DiscordOAuthManager._get_user_roles(user_id)
|
||||||
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/members/" + str(user_id)
|
for g in group_ids:
|
||||||
data = {'roles': group_ids}
|
if g not in user_group_ids:
|
||||||
r = requests.patch(path, headers=custom_headers, json=data)
|
DiscordOAuthManager._modify_user_role(user_id, g, 'put')
|
||||||
logger.debug("Received status code %s after setting user roles" % r.status_code)
|
time.sleep(1) # we're gonna be hammering the API here
|
||||||
r.raise_for_status()
|
for g in user_group_ids:
|
||||||
|
if g not in group_ids:
|
||||||
|
DiscordOAuthManager._modify_user_role(user_id, g, 'delete')
|
||||||
|
time.sleep(1)
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ from django.contrib.auth.models import User
|
|||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from allianceauth.notifications import notify
|
from allianceauth.notifications import notify
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
|
from requests.exceptions import HTTPError
|
||||||
from allianceauth.services.hooks import NameFormatter
|
from allianceauth.services.hooks import NameFormatter
|
||||||
from .manager import DiscordOAuthManager, DiscordApiBackoff
|
from .manager import DiscordOAuthManager, DiscordApiBackoff
|
||||||
from .models import DiscordUser
|
from .models import DiscordUser
|
||||||
|
from allianceauth.services.tasks import QueueOnce
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -19,15 +20,16 @@ class DiscordTasks:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add_user(cls, user, code):
|
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:
|
if user_id:
|
||||||
discord_user = DiscordUser()
|
discord_user = DiscordUser()
|
||||||
discord_user.user = user
|
discord_user.user = user
|
||||||
discord_user.uid = user_id
|
discord_user.uid = user_id
|
||||||
discord_user.save()
|
discord_user.save()
|
||||||
if settings.DISCORD_SYNC_NAMES:
|
|
||||||
cls.update_nickname.delay(user.pk)
|
|
||||||
cls.update_groups.delay(user.pk)
|
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -57,28 +59,32 @@ class DiscordTasks:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@shared_task(bind=True, name='discord.update_groups')
|
@shared_task(bind=True, name='discord.update_groups', base=QueueOnce)
|
||||||
def update_groups(task_self, pk):
|
def update_groups(self, pk):
|
||||||
user = User.objects.get(pk=pk)
|
user = User.objects.get(pk=pk)
|
||||||
logger.debug("Updating discord groups for user %s" % user)
|
logger.debug("Updating discord groups for user %s" % user)
|
||||||
if DiscordTasks.has_account(user):
|
if DiscordTasks.has_account(user):
|
||||||
groups = []
|
groups = DiscordTasks.get_groups(user)
|
||||||
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 discord groups to %s" % (user, groups))
|
logger.debug("Updating user %s discord groups to %s" % (user, groups))
|
||||||
try:
|
try:
|
||||||
DiscordOAuthManager.update_groups(user.discord.uid, groups)
|
DiscordOAuthManager.update_groups(user.discord.uid, groups)
|
||||||
except DiscordApiBackoff as bo:
|
except DiscordApiBackoff as bo:
|
||||||
logger.info("Discord group sync API back off for %s, "
|
logger.info("Discord group sync API back off for %s, "
|
||||||
"retrying in %s seconds" % (user, bo.retry_after_seconds))
|
"retrying in %s seconds" % (user, bo.retry_after_seconds))
|
||||||
raise task_self.retry(countdown=bo.retry_after_seconds)
|
raise 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:
|
except Exception as e:
|
||||||
if task_self:
|
if self:
|
||||||
logger.exception("Discord group sync failed for %s, retrying in 10 mins" % user)
|
logger.exception("Discord group sync failed for %s, retrying in 10 mins" % user)
|
||||||
raise task_self.retry(countdown=60 * 10)
|
raise self.retry(countdown=60 * 10)
|
||||||
else:
|
else:
|
||||||
# Rethrow
|
# Rethrow
|
||||||
raise e
|
raise e
|
||||||
@@ -94,7 +100,7 @@ class DiscordTasks:
|
|||||||
DiscordTasks.update_groups.delay(discord_user.user.pk)
|
DiscordTasks.update_groups.delay(discord_user.user.pk)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@shared_task(bind=True, name='discord.update_nickname')
|
@shared_task(bind=True, name='discord.update_nickname', base=QueueOnce)
|
||||||
def update_nickname(self, pk):
|
def update_nickname(self, pk):
|
||||||
user = User.objects.get(pk=pk)
|
user = User.objects.get(pk=pk)
|
||||||
logger.debug("Updating discord nickname for user %s" % user)
|
logger.debug("Updating discord nickname for user %s" % user)
|
||||||
@@ -104,6 +110,10 @@ class DiscordTasks:
|
|||||||
logger.debug("Updating user %s discord nickname to %s" % (user, character.character_name))
|
logger.debug("Updating user %s discord nickname to %s" % (user, character.character_name))
|
||||||
try:
|
try:
|
||||||
DiscordOAuthManager.update_nickname(user.discord.uid, DiscordTasks.get_nickname(user))
|
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 self.retry(countdown=bo.retry_after_seconds)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if self:
|
if self:
|
||||||
logger.exception("Discord nickname sync failed for %s, retrying in 10 mins" % user)
|
logger.exception("Discord nickname sync failed for %s, retrying in 10 mins" % user)
|
||||||
@@ -132,3 +142,7 @@ class DiscordTasks:
|
|||||||
def get_nickname(user):
|
def get_nickname(user):
|
||||||
from .auth_hooks import DiscordService
|
from .auth_hooks import DiscordService
|
||||||
return NameFormatter(DiscordService(), user).format_name()
|
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):
|
class DiscordViewsTestCase(WebTest):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.member = AuthUtils.create_member('auth_member')
|
self.member = AuthUtils.create_member('auth_member')
|
||||||
|
AuthUtils.add_main_character(self.member, 'test character', '1234', '2345', 'test corp', 'testc')
|
||||||
add_permissions()
|
add_permissions()
|
||||||
|
|
||||||
def login(self):
|
def login(self):
|
||||||
@@ -198,11 +199,11 @@ class DiscordManagerTestCase(TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test__sanitize_groupname(self):
|
def test__sanitize_group_name(self):
|
||||||
test_group_name = ' Group Name_Test_'
|
test_group_name = str(10**103)
|
||||||
group_name = DiscordOAuthManager._sanitize_groupname(test_group_name)
|
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):
|
def test_generate_Bot_add_url(self):
|
||||||
bot_add_url = DiscordOAuthManager.generate_bot_add_url()
|
bot_add_url = DiscordOAuthManager.generate_bot_add_url()
|
||||||
@@ -245,18 +246,20 @@ class DiscordManagerTestCase(TestCase):
|
|||||||
|
|
||||||
headers = {'accept': 'application/json', 'authorization': 'Bearer accesstoken'}
|
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',
|
m.register_uri('GET',
|
||||||
manager.DISCORD_URL + "/users/@me",
|
manager.DISCORD_URL + "/users/@me",
|
||||||
request_headers=headers,
|
request_headers=headers,
|
||||||
text=json.dumps({'id': "123456"}))
|
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
|
# Act
|
||||||
return_value = DiscordOAuthManager.add_user('abcdef')
|
return_value = DiscordOAuthManager.add_user('abcdef', [])
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
self.assertEqual(return_value, '123456')
|
self.assertEqual(return_value, '123456')
|
||||||
@@ -324,54 +327,54 @@ class DiscordManagerTestCase(TestCase):
|
|||||||
# Assert
|
# Assert
|
||||||
self.assertTrue(result)
|
self.assertTrue(result)
|
||||||
|
|
||||||
|
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._get_user_roles')
|
||||||
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._get_groups')
|
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._get_groups')
|
||||||
@requests_mock.Mocker()
|
@requests_mock.Mocker()
|
||||||
def test_update_groups(self, group_cache, m):
|
def test_update_groups(self, group_cache, user_roles, m):
|
||||||
# Arrange
|
# Arrange
|
||||||
groups = ['Member', 'Blue', 'Special Group']
|
groups = ['Member', 'Blue', 'SpecialGroup']
|
||||||
|
|
||||||
group_cache.return_value = [{'id': 111, 'name': 'Member'},
|
group_cache.return_value = [{'id': '111', 'name': 'Member'},
|
||||||
{'id': 222, 'name': 'Blue'},
|
{'id': '222', 'name': 'Blue'},
|
||||||
{'id': 333, 'name': 'SpecialGroup'},
|
{'id': '333', 'name': 'SpecialGroup'},
|
||||||
{'id': 444, 'name': 'NotYourGroup'}]
|
{'id': '444', 'name': 'NotYourGroup'}]
|
||||||
|
user_roles.return_value = ['444']
|
||||||
|
|
||||||
headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
||||||
user_id = 12345
|
user_id = 12345
|
||||||
request_url = '{}/guilds/{}/members/{}'.format(manager.DISCORD_URL, settings.DISCORD_GUILD_ID, user_id)
|
user_request_url = '{}/guilds/{}/members/{}'.format(manager.DISCORD_URL, settings.DISCORD_GUILD_ID, user_id)
|
||||||
|
group_request_urls = ['{}/guilds/{}/members/{}/roles/{}'.format(manager.DISCORD_URL, settings.DISCORD_GUILD_ID, user_id, g['id']) for g in group_cache.return_value]
|
||||||
|
|
||||||
m.patch(request_url,
|
m.patch(user_request_url, request_headers=headers)
|
||||||
request_headers=headers)
|
[m.put(url, request_headers=headers) for url in group_request_urls[:-1]]
|
||||||
|
m.delete(group_request_urls[-1], request_headers=headers)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
DiscordOAuthManager.update_groups(user_id, groups)
|
DiscordOAuthManager.update_groups(user_id, groups)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
self.assertEqual(len(m.request_history), 1, 'Must be one HTTP call made')
|
self.assertEqual(len(m.request_history), 4, 'Must be 4 HTTP calls made')
|
||||||
history = json.loads(m.request_history[0].text)
|
|
||||||
self.assertIn('roles', history, "'The request must send JSON object with the 'roles' key")
|
|
||||||
self.assertIn(111, history['roles'], 'The group id 111 must be added to the request')
|
|
||||||
self.assertIn(222, history['roles'], 'The group id 222 must be added to the request')
|
|
||||||
self.assertIn(333, history['roles'], 'The group id 333 must be added to the request')
|
|
||||||
self.assertNotIn(444, history['roles'], 'The group id 444 must NOT be added to the request')
|
|
||||||
|
|
||||||
@mock.patch(MODULE_PATH + '.manager.cache')
|
@mock.patch(MODULE_PATH + '.manager.cache')
|
||||||
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._get_groups')
|
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._get_user_roles')
|
||||||
|
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._group_name_to_id')
|
||||||
@requests_mock.Mocker()
|
@requests_mock.Mocker()
|
||||||
def test_update_groups_backoff(self, group_cache, djcache, m):
|
def test_update_groups_backoff(self, name_to_id, user_groups, djcache, m):
|
||||||
# Arrange
|
# Arrange
|
||||||
groups = ['Member']
|
groups = ['Member']
|
||||||
group_cache.return_value = [{'id': 111, 'name': 'Member'}]
|
user_groups.return_value = []
|
||||||
|
name_to_id.return_value = '111'
|
||||||
|
|
||||||
headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
||||||
user_id = 12345
|
user_id = 12345
|
||||||
request_url = '{}/guilds/{}/members/{}'.format(manager.DISCORD_URL, settings.DISCORD_GUILD_ID, user_id)
|
request_url = '{}/guilds/{}/members/{}/roles/{}'.format(manager.DISCORD_URL, settings.DISCORD_GUILD_ID, user_id, name_to_id.return_value)
|
||||||
|
|
||||||
djcache.get.return_value = None # No existing backoffs in cache
|
djcache.get.return_value = None # No existing backoffs in cache
|
||||||
|
|
||||||
m.patch(request_url,
|
m.put(request_url,
|
||||||
request_headers=headers,
|
request_headers=headers,
|
||||||
headers={'Retry-After': '200000'},
|
headers={'Retry-After': '200000'},
|
||||||
status_code=429)
|
status_code=429)
|
||||||
|
|
||||||
# Act & Assert
|
# Act & Assert
|
||||||
with self.assertRaises(manager.DiscordApiBackoff) as bo:
|
with self.assertRaises(manager.DiscordApiBackoff) as bo:
|
||||||
@@ -388,23 +391,25 @@ class DiscordManagerTestCase(TestCase):
|
|||||||
self.assertTrue(datetime.datetime.strptime(args[1], manager.cache_time_format) > datetime.datetime.now())
|
self.assertTrue(datetime.datetime.strptime(args[1], manager.cache_time_format) > datetime.datetime.now())
|
||||||
|
|
||||||
@mock.patch(MODULE_PATH + '.manager.cache')
|
@mock.patch(MODULE_PATH + '.manager.cache')
|
||||||
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._get_groups')
|
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._get_user_roles')
|
||||||
|
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._group_name_to_id')
|
||||||
@requests_mock.Mocker()
|
@requests_mock.Mocker()
|
||||||
def test_update_groups_global_backoff(self, group_cache, djcache, m):
|
def test_update_groups_global_backoff(self, name_to_id, user_groups, djcache, m):
|
||||||
# Arrange
|
# Arrange
|
||||||
groups = ['Member']
|
groups = ['Member']
|
||||||
group_cache.return_value = [{'id': 111, 'name': 'Member'}]
|
user_groups.return_value = []
|
||||||
|
name_to_id.return_value = '111'
|
||||||
|
|
||||||
headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
||||||
user_id = 12345
|
user_id = 12345
|
||||||
request_url = '{}/guilds/{}/members/{}'.format(manager.DISCORD_URL, settings.DISCORD_GUILD_ID, user_id)
|
request_url = '{}/guilds/{}/members/{}/roles/{}'.format(manager.DISCORD_URL, settings.DISCORD_GUILD_ID, user_id, name_to_id.return_value)
|
||||||
|
|
||||||
djcache.get.return_value = None # No existing backoffs in cache
|
djcache.get.return_value = None # No existing backoffs in cache
|
||||||
|
|
||||||
m.patch(request_url,
|
m.put(request_url,
|
||||||
request_headers=headers,
|
request_headers=headers,
|
||||||
headers={'Retry-After': '200000', 'X-RateLimit-Global': 'true'},
|
headers={'Retry-After': '200000', 'X-RateLimit-Global': 'true'},
|
||||||
status_code=429)
|
status_code=429)
|
||||||
|
|
||||||
# Act & Assert
|
# Act & Assert
|
||||||
with self.assertRaises(manager.DiscordApiBackoff) as bo:
|
with self.assertRaises(manager.DiscordApiBackoff) as bo:
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from hashlib import md5
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
GROUP_CACHE_MAX_AGE = int(getattr(settings, 'DISCOURSE_GROUP_CACHE_MAX_AGE', 2 * 60 * 60)) # default 2 hours
|
GROUP_CACHE_MAX_AGE = getattr(settings, 'DISCOURSE_GROUP_CACHE_MAX_AGE', 2 * 60 * 60) # default 2 hours
|
||||||
|
|
||||||
|
|
||||||
class DiscourseError(Exception):
|
class DiscourseError(Exception):
|
||||||
@@ -23,7 +23,7 @@ class DiscourseError(Exception):
|
|||||||
ENDPOINTS = {
|
ENDPOINTS = {
|
||||||
'groups': {
|
'groups': {
|
||||||
'list': {
|
'list': {
|
||||||
'path': "/admin/groups.json",
|
'path': "/groups/search.json",
|
||||||
'method': 'get',
|
'method': 'get',
|
||||||
'args': {
|
'args': {
|
||||||
'required': [],
|
'required': [],
|
||||||
@@ -345,7 +345,7 @@ class DiscourseManager:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update_groups(user):
|
def update_groups(user):
|
||||||
groups = []
|
groups = [DiscourseManager._sanitize_groupname(user.profile.state.name)]
|
||||||
for g in user.groups.all():
|
for g in user.groups.all():
|
||||||
groups.append(DiscourseManager._sanitize_groupname(str(g)))
|
groups.append(DiscourseManager._sanitize_groupname(str(g)))
|
||||||
logger.debug("Updating discourse user %s groups to %s" % (user, groups))
|
logger.debug("Updating discourse user %s groups to %s" % (user, groups))
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from celery import shared_task
|
|||||||
|
|
||||||
from allianceauth.notifications import notify
|
from allianceauth.notifications import notify
|
||||||
from allianceauth.services.hooks import NameFormatter
|
from allianceauth.services.hooks import NameFormatter
|
||||||
|
from allianceauth.services.tasks import QueueOnce
|
||||||
from .manager import DiscourseManager
|
from .manager import DiscourseManager
|
||||||
from .models import DiscourseUser
|
from .models import DiscourseUser
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ class DiscourseTasks:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@shared_task(bind=True, name='discourse.update_groups')
|
@shared_task(bind=True, name='discourse.update_groups', base=QueueOnce)
|
||||||
def update_groups(self, pk):
|
def update_groups(self, pk):
|
||||||
user = User.objects.get(pk=pk)
|
user = User.objects.get(pk=pk)
|
||||||
logger.debug("Updating discourse groups for user %s" % user)
|
logger.debug("Updating discourse groups for user %s" % user)
|
||||||
|
|||||||
@@ -4,16 +4,20 @@ import string
|
|||||||
import re
|
import re
|
||||||
from django.db import connections
|
from django.db import connections
|
||||||
from passlib.hash import bcrypt
|
from passlib.hash import bcrypt
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
TABLE_PREFIX = getattr(settings, 'IPS4_TABLE_PREFIX', '')
|
||||||
|
|
||||||
|
|
||||||
class Ips4Manager:
|
class Ips4Manager:
|
||||||
SQL_ADD_USER = r"INSERT INTO core_members (name, email, members_pass_hash, members_pass_salt, " \
|
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)"
|
r"member_group_id) VALUES (%%s, %%s, %%s, %%s, %%s)" % TABLE_PREFIX
|
||||||
SQL_GET_ID = r"SELECT member_id FROM core_members WHERE name = %s"
|
SQL_GET_ID = r"SELECT member_id FROM %score_members WHERE name = %%s" % TABLE_PREFIX
|
||||||
SQL_UPDATE_PASSWORD = r"UPDATE core_members SET members_pass_hash = %s, members_pass_salt = %s WHERE name = %s"
|
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 core_members WHERE member_id = %s"
|
SQL_DEL_USER = r"DELETE FROM %score_members WHERE member_id = %%s" % TABLE_PREFIX
|
||||||
|
|
||||||
MEMBER_GROUP_ID = 3
|
MEMBER_GROUP_ID = 3
|
||||||
|
|
||||||
|
|||||||
@@ -5,26 +5,30 @@ import re
|
|||||||
|
|
||||||
from django.db import connections
|
from django.db import connections
|
||||||
from passlib.hash import bcrypt
|
from passlib.hash import bcrypt
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
# requires yum install libffi-devel and pip install bcrypt
|
# requires yum install libffi-devel and pip install bcrypt
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
TABLE_PREFIX = getattr(settings, 'MARKET_TABLE_PREFIX', 'fos_')
|
||||||
|
|
||||||
|
|
||||||
class MarketManager:
|
class MarketManager:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
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"password, locked, expired, roles, credentials_expired, characterid, characterName)" \
|
||||||
r"VALUES (%s, %s, %s, %s, 1,%s, %s, 0, 0, 'a:0:{}', 0, %s, %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 fos_user WHERE username = %s"
|
SQL_GET_USER_ID = r"SELECT id FROM %suser WHERE username = %%s" % TABLE_PREFIX
|
||||||
SQL_DISABLE_USER = r"UPDATE fos_user SET enabled = '0' WHERE username = %s"
|
SQL_DISABLE_USER = r"UPDATE %suser SET enabled = '0' WHERE username = %%s" % TABLE_PREFIX
|
||||||
SQL_ENABLE_USER = r"UPDATE fos_user SET enabled = '1' WHERE username = %s"
|
SQL_ENABLE_USER = r"UPDATE %suser SET enabled = '1' WHERE username = %%s" % TABLE_PREFIX
|
||||||
SQL_UPDATE_PASSWORD = r"UPDATE fos_user SET password = %s, salt = %s WHERE username = %s"
|
SQL_UPDATE_PASSWORD = r"UPDATE %suser SET password = %%s, salt = %%s WHERE username = %%s" % TABLE_PREFIX
|
||||||
SQL_CHECK_EMAIL = r"SELECT email FROM fos_user WHERE email = %s"
|
SQL_CHECK_EMAIL = r"SELECT email FROM %suser WHERE email = %%s" % TABLE_PREFIX
|
||||||
SQL_CHECK_USERNAME = r"SELECT username FROM fos_user WHERE username = %s"
|
SQL_CHECK_USERNAME = r"SELECT username FROM %suser WHERE username = %%s" % TABLE_PREFIX
|
||||||
SQL_UPDATE_USER = r"UPDATE fos_user SET password = %s, salt = %s, enabled = '1' WHERE username = %s"
|
SQL_UPDATE_USER = r"UPDATE %suser SET password = %%s, salt = %%s, enabled = '1' WHERE username = %%s" % TABLE_PREFIX
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __santatize_username(username):
|
def __santatize_username(username):
|
||||||
|
|||||||
@@ -82,11 +82,9 @@ class MumbleUser(AbstractServiceModel):
|
|||||||
def update_groups(self, groups: Group=None):
|
def update_groups(self, groups: Group=None):
|
||||||
if groups is None:
|
if groups is None:
|
||||||
groups = self.user.groups.all()
|
groups = self.user.groups.all()
|
||||||
groups_str = []
|
groups_str = [self.user.profile.state.name]
|
||||||
for group in groups:
|
for group in groups:
|
||||||
groups_str.append(str(group.name))
|
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]))
|
safe_groups = ','.join(set([g.replace(' ', '-') for g in groups_str]))
|
||||||
logger.info("Updating mumble user {} groups to {}".format(self.user, safe_groups))
|
logger.info("Updating mumble user {} groups to {}".format(self.user, safe_groups))
|
||||||
self.groups = safe_groups
|
self.groups = safe_groups
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import logging
|
|||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
|
from allianceauth.services.tasks import QueueOnce
|
||||||
from .models import MumbleUser
|
from .models import MumbleUser
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -26,7 +26,7 @@ class MumbleTasks:
|
|||||||
MumbleUser.objects.all().delete()
|
MumbleUser.objects.all().delete()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@shared_task(bind=True, name="mumble.update_groups")
|
@shared_task(bind=True, name="mumble.update_groups", base=QueueOnce)
|
||||||
def update_groups(self, pk):
|
def update_groups(self, pk):
|
||||||
user = User.objects.get(pk=pk)
|
user = User.objects.get(pk=pk)
|
||||||
logger.debug("Updating mumble groups for user %s" % user)
|
logger.debug("Updating mumble groups for user %s" % user)
|
||||||
|
|||||||
@@ -136,7 +136,9 @@ class MumbleViewsTestCase(TestCase):
|
|||||||
mumble_user = MumbleUser.objects.get(user=self.member)
|
mumble_user = MumbleUser.objects.get(user=self.member)
|
||||||
self.assertEqual(mumble_user.username, expected_username)
|
self.assertEqual(mumble_user.username, expected_username)
|
||||||
self.assertTrue(mumble_user.pwhash)
|
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):
|
def test_deactivate_post(self):
|
||||||
self.login()
|
self.login()
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from django.contrib.auth.models import User
|
|||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from allianceauth.notifications import notify
|
from allianceauth.notifications import notify
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
|
from allianceauth.services.tasks import QueueOnce
|
||||||
from allianceauth.services.modules.openfire.manager import OpenfireManager
|
from allianceauth.services.modules.openfire.manager import OpenfireManager
|
||||||
from allianceauth.services.hooks import NameFormatter
|
from allianceauth.services.hooks import NameFormatter
|
||||||
from .models import OpenfireUser
|
from .models import OpenfireUser
|
||||||
@@ -40,16 +40,14 @@ class OpenfireTasks:
|
|||||||
OpenfireUser.objects.all().delete()
|
OpenfireUser.objects.all().delete()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@shared_task(bind=True, name="openfire.update_groups")
|
@shared_task(bind=True, name="openfire.update_groups", base=QueueOnce)
|
||||||
def update_groups(self, pk):
|
def update_groups(self, pk):
|
||||||
user = User.objects.get(pk=pk)
|
user = User.objects.get(pk=pk)
|
||||||
logger.debug("Updating jabber groups for user %s" % user)
|
logger.debug("Updating jabber groups for user %s" % user)
|
||||||
if OpenfireTasks.has_account(user):
|
if OpenfireTasks.has_account(user):
|
||||||
groups = []
|
groups = [user.profile.state.name]
|
||||||
for group in user.groups.all():
|
for group in user.groups.all():
|
||||||
groups.append(str(group.name))
|
groups.append(str(group.name))
|
||||||
if len(groups) == 0:
|
|
||||||
groups.append('empty')
|
|
||||||
logger.debug("Updating user %s jabber groups to %s" % (user, groups))
|
logger.debug("Updating user %s jabber groups to %s" % (user, groups))
|
||||||
try:
|
try:
|
||||||
OpenfireManager.update_user_groups(user.openfire.username, groups)
|
OpenfireManager.update_user_groups(user.openfire.username, groups)
|
||||||
|
|||||||
@@ -14,40 +14,43 @@ from django.conf import settings
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
TABLE_PREFIX = getattr(settings, 'PHPBB3_TABLE_PREFIX', 'phpbb_')
|
||||||
|
|
||||||
|
|
||||||
class Phpbb3Manager:
|
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_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 " \
|
SQL_GET_USER_GROUPS = r"SELECT %(prefix)sgroups.group_name FROM %(prefix)sgroups , %(prefix)suser_group WHERE " \
|
||||||
r"phpbb_user_group.group_id = phpbb_groups.group_id AND user_id=%s"
|
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, " \
|
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"
|
"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):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import logging
|
|||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
|
from allianceauth.services.tasks import QueueOnce
|
||||||
from allianceauth.notifications import notify
|
from allianceauth.notifications import notify
|
||||||
from allianceauth.services.hooks import NameFormatter
|
from allianceauth.services.hooks import NameFormatter
|
||||||
from .manager import Phpbb3Manager
|
from .manager import Phpbb3Manager
|
||||||
@@ -35,16 +35,14 @@ class Phpbb3Tasks:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@shared_task(bind=True, name="phpbb3.update_groups")
|
@shared_task(bind=True, name="phpbb3.update_groups", base=QueueOnce)
|
||||||
def update_groups(self, pk):
|
def update_groups(self, pk):
|
||||||
user = User.objects.get(pk=pk)
|
user = User.objects.get(pk=pk)
|
||||||
logger.debug("Updating phpbb3 groups for user %s" % user)
|
logger.debug("Updating phpbb3 groups for user %s" % user)
|
||||||
if Phpbb3Tasks.has_account(user):
|
if Phpbb3Tasks.has_account(user):
|
||||||
groups = []
|
groups = [user.profile.state.name]
|
||||||
for group in user.groups.all():
|
for group in user.groups.all():
|
||||||
groups.append(str(group.name))
|
groups.append(str(group.name))
|
||||||
if len(groups) == 0:
|
|
||||||
groups.append('empty')
|
|
||||||
logger.debug("Updating user %s phpbb3 groups to %s" % (user, groups))
|
logger.debug("Updating user %s phpbb3 groups to %s" % (user, groups))
|
||||||
try:
|
try:
|
||||||
Phpbb3Manager.update_groups(user.phpbb3.username, groups)
|
Phpbb3Manager.update_groups(user.phpbb3.username, groups)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import logging
|
|||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
|
from allianceauth.services.tasks import QueueOnce
|
||||||
from allianceauth.notifications import notify
|
from allianceauth.notifications import notify
|
||||||
from allianceauth.services.hooks import NameFormatter
|
from allianceauth.services.hooks import NameFormatter
|
||||||
from .manager import SeatManager
|
from .manager import SeatManager
|
||||||
@@ -34,17 +34,14 @@ class SeatTasks:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@shared_task(bind=True)
|
@shared_task(bind=True, name='seat.update_roles', base=QueueOnce)
|
||||||
def update_roles(self, pk):
|
def update_roles(self, pk):
|
||||||
user = User.objects.get(pk=pk)
|
user = User.objects.get(pk=pk)
|
||||||
logger.debug("Updating SeAT roles for user %s" % user)
|
logger.debug("Updating SeAT roles for user %s" % user)
|
||||||
groups = []
|
|
||||||
if SeatTasks.has_account(user):
|
if SeatTasks.has_account(user):
|
||||||
|
groups = [user.profile.state.name]
|
||||||
for group in user.groups.all():
|
for group in user.groups.all():
|
||||||
groups.append(str(group.name))
|
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))
|
logger.debug("Updating user %s SeAT roles to %s" % (user, groups))
|
||||||
try:
|
try:
|
||||||
SeatManager.update_roles(user.seat.username, groups)
|
SeatManager.update_roles(user.seat.username, groups)
|
||||||
|
|||||||
@@ -152,7 +152,6 @@ class SeatViewsTestCase(TestCase):
|
|||||||
self.assertContains(response, expected_username)
|
self.assertContains(response, expected_username)
|
||||||
seat_user = SeatUser.objects.get(user=self.member)
|
seat_user = SeatUser.objects.get(user=self.member)
|
||||||
self.assertEqual(seat_user.username, expected_username)
|
self.assertEqual(seat_user.username, expected_username)
|
||||||
self.assertTrue(manager.synchronize_eveapis.called)
|
|
||||||
|
|
||||||
@mock.patch(MODULE_PATH + '.tasks.SeatManager')
|
@mock.patch(MODULE_PATH + '.tasks.SeatManager')
|
||||||
def test_deactivate(self, manager):
|
def test_deactivate(self, manager):
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ def activate_seat(request):
|
|||||||
logger.info("Successfully activated SeAT for user %s" % request.user)
|
logger.info("Successfully activated SeAT for user %s" % request.user)
|
||||||
messages.add_message(request, messages.SUCCESS, _('Successfully activated your %(service)s account.') %
|
messages.add_message(request, messages.SUCCESS, _('Successfully activated your %(service)s account.') %
|
||||||
SERVICE_NAME)
|
SERVICE_NAME)
|
||||||
SeatManager.synchronize_eveapis(request.user)
|
|
||||||
credentials = {
|
credentials = {
|
||||||
'username': request.user.seat.username,
|
'username': request.user.seat.username,
|
||||||
'password': result[1],
|
'password': result[1],
|
||||||
|
|||||||
@@ -12,35 +12,38 @@ from django.conf import settings
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
TABLE_PREFIX = getattr(settings, 'SMF_TABLE_PREFIX', 'smf_')
|
||||||
|
|
||||||
|
|
||||||
class SmfManager:
|
class SmfManager:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
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" 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
|
@staticmethod
|
||||||
def _sanitize_groupname(name):
|
def _sanitize_groupname(name):
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import logging
|
|||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
|
from allianceauth.services.tasks import QueueOnce
|
||||||
from allianceauth.notifications import notify
|
from allianceauth.notifications import notify
|
||||||
from allianceauth.services.hooks import NameFormatter
|
from allianceauth.services.hooks import NameFormatter
|
||||||
from .manager import SmfManager
|
from .manager import SmfManager
|
||||||
@@ -39,16 +39,14 @@ class SmfTasks:
|
|||||||
SmfUser.objects.all().delete()
|
SmfUser.objects.all().delete()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@shared_task(bind=True, name="smf.update_groups")
|
@shared_task(bind=True, name="smf.update_groups", base=QueueOnce)
|
||||||
def update_groups(self, pk):
|
def update_groups(self, pk):
|
||||||
user = User.objects.get(pk=pk)
|
user = User.objects.get(pk=pk)
|
||||||
logger.debug("Updating smf groups for user %s" % user)
|
logger.debug("Updating smf groups for user %s" % user)
|
||||||
if SmfTasks.has_account(user):
|
if SmfTasks.has_account(user):
|
||||||
groups = []
|
groups = [user.profile.state.name]
|
||||||
for group in user.groups.all():
|
for group in user.groups.all():
|
||||||
groups.append(str(group.name))
|
groups.append(str(group.name))
|
||||||
if len(groups) == 0:
|
|
||||||
groups.append('empty')
|
|
||||||
logger.debug("Updating user %s smf groups to %s" % (user, groups))
|
logger.debug("Updating user %s smf groups to %s" % (user, groups))
|
||||||
try:
|
try:
|
||||||
SmfManager.update_groups(user.smf.username, groups)
|
SmfManager.update_groups(user.smf.username, groups)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from .models import AuthTS, Teamspeak3User
|
from .models import AuthTS, Teamspeak3User, StateGroup
|
||||||
|
|
||||||
|
|
||||||
class Teamspeak3UserAdmin(admin.ModelAdmin):
|
class Teamspeak3UserAdmin(admin.ModelAdmin):
|
||||||
@@ -12,5 +12,11 @@ class AuthTSgroupAdmin(admin.ModelAdmin):
|
|||||||
filter_horizontal = ('ts_group',)
|
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(AuthTS, AuthTSgroupAdmin)
|
||||||
admin.site.register(Teamspeak3User, Teamspeak3UserAdmin)
|
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.db import models
|
||||||
from django.contrib.auth.models import User, Group
|
from django.contrib.auth.models import User, Group
|
||||||
|
from allianceauth.authentication.models import State
|
||||||
|
|
||||||
|
|
||||||
class Teamspeak3User(models.Model):
|
class Teamspeak3User(models.Model):
|
||||||
@@ -50,3 +51,8 @@ class UserTSgroup(models.Model):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.user.name
|
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_delete
|
||||||
from django.db.models.signals import post_save
|
from django.db.models.signals import post_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
from allianceauth.authentication.signals import state_changed
|
||||||
from .tasks import Teamspeak3Tasks
|
from .tasks import Teamspeak3Tasks
|
||||||
from .models import AuthTS
|
from .models import AuthTS, StateGroup
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -34,3 +34,16 @@ def post_save_authts(sender, instance, *args, **kwargs):
|
|||||||
def post_delete_authts(sender, instance, *args, **kwargs):
|
def post_delete_authts(sender, instance, *args, **kwargs):
|
||||||
logger.debug("Received post_delete signal from %s" % instance)
|
logger.debug("Received post_delete signal from %s" % instance)
|
||||||
transaction.on_commit(trigger_all_ts_update)
|
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)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import logging
|
|||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
|
from allianceauth.services.tasks import QueueOnce
|
||||||
from allianceauth.notifications import notify
|
from allianceauth.notifications import notify
|
||||||
from allianceauth.services.hooks import NameFormatter
|
from allianceauth.services.hooks import NameFormatter
|
||||||
from .manager import Teamspeak3Manager
|
from .manager import Teamspeak3Manager
|
||||||
@@ -56,7 +56,7 @@ class Teamspeak3Tasks:
|
|||||||
logger.info("Teamspeak3 disabled")
|
logger.info("Teamspeak3 disabled")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@shared_task(bind=True, name="teamspeak3.update_groups")
|
@shared_task(bind=True, name="teamspeak3.update_groups", base=QueueOnce)
|
||||||
def update_groups(self, pk):
|
def update_groups(self, pk):
|
||||||
user = User.objects.get(pk=pk)
|
user = User.objects.get(pk=pk)
|
||||||
logger.debug("Updating user %s teamspeak3 groups" % user)
|
logger.debug("Updating user %s teamspeak3 groups" % user)
|
||||||
@@ -69,6 +69,8 @@ class Teamspeak3Tasks:
|
|||||||
for filtered_group in filtered_groups:
|
for filtered_group in filtered_groups:
|
||||||
for ts_group in filtered_group.ts_group.all():
|
for ts_group in filtered_group.ts_group.all():
|
||||||
groups[ts_group.ts_group_name] = ts_group.ts_group_id
|
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))
|
logger.debug("Updating user %s teamspeak3 groups to %s" % (user, groups))
|
||||||
try:
|
try:
|
||||||
with Teamspeak3Manager() as ts3man:
|
with Teamspeak3Manager() as ts3man:
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<h1 class="page-header text-center">{% trans "Verify Teamspeak Identity" %}</h1>
|
<h1 class="page-header text-center">{% trans "Verify Teamspeak Identity" %}</h1>
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="col-md-4 col-md-offset-4">
|
<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/>
|
<br/>
|
||||||
<form class="form-signin" role="form" action="{% url 'teamspeak3:verify' %}" method="POST">
|
<form class="form-signin" role="form" action="{% url 'teamspeak3:verify' %}" method="POST">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|||||||
@@ -7,9 +7,8 @@ from django.core.exceptions import ObjectDoesNotExist
|
|||||||
from django.db.models import signals
|
from django.db.models import signals
|
||||||
|
|
||||||
from allianceauth.tests.auth_utils import AuthUtils
|
from allianceauth.tests.auth_utils import AuthUtils
|
||||||
|
|
||||||
from .auth_hooks import Teamspeak3Service
|
from .auth_hooks import Teamspeak3Service
|
||||||
from .models import Teamspeak3User, AuthTS, TSgroup
|
from .models import Teamspeak3User, AuthTS, TSgroup, StateGroup
|
||||||
from .tasks import Teamspeak3Tasks
|
from .tasks import Teamspeak3Tasks
|
||||||
from .signals import m2m_changed_authts_group, post_save_authts, post_delete_authts
|
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)
|
member = AuthUtils.create_member(self.member)
|
||||||
Teamspeak3User.objects.create(user=member, uid=self.member, perm_key='123ABC')
|
Teamspeak3User.objects.create(user=member, uid=self.member, perm_key='123ABC')
|
||||||
self.none_user = 'none_user'
|
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_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 = AuthTS.objects.create(auth_group=member.groups.all()[0])
|
||||||
m2m_member_group.ts_group.add(ts_member_group)
|
m2m_member_group.ts_group.add(ts_member_group)
|
||||||
m2m_member_group.save()
|
m2m_member_group.save()
|
||||||
|
StateGroup.objects.create(state=state, ts_group=ts_state_group)
|
||||||
self.service = Teamspeak3Service
|
self.service = Teamspeak3Service
|
||||||
add_permissions()
|
add_permissions()
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ class Teamspeak3HooksTestCase(TestCase):
|
|||||||
instance = manager.return_value.__enter__.return_value
|
instance = manager.return_value.__enter__.return_value
|
||||||
service = self.service()
|
service = self.service()
|
||||||
service.update_all_groups()
|
service.update_all_groups()
|
||||||
# Check member and blue user have groups updated
|
# Check user has groups updated
|
||||||
self.assertTrue(instance.update_groups.called)
|
self.assertTrue(instance.update_groups.called)
|
||||||
self.assertEqual(instance.update_groups.call_count, 1)
|
self.assertEqual(instance.update_groups.call_count, 1)
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ class Teamspeak3HooksTestCase(TestCase):
|
|||||||
self.assertTrue(instance.update_groups.called)
|
self.assertTrue(instance.update_groups.called)
|
||||||
args, kwargs = instance.update_groups.call_args
|
args, kwargs = instance.update_groups.call_args
|
||||||
# update_groups(user.teamspeak3.uid, groups)
|
# 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
|
self.assertEqual(self.member, args[0]) # Check uid
|
||||||
|
|
||||||
# Check none user does not have groups updated
|
# Check none user does not have groups updated
|
||||||
@@ -278,3 +278,15 @@ class Teamspeak3SignalsTestCase(TestCase):
|
|||||||
self.m2m_member.delete() # Trigger delete signal
|
self.m2m_member.delete() # Trigger delete signal
|
||||||
|
|
||||||
self.assertTrue(trigger_all_ts_update.called)
|
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)
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ module_urls = [
|
|||||||
name='activate'),
|
name='activate'),
|
||||||
url(r'^deactivate/$', views.deactivate_teamspeak3,
|
url(r'^deactivate/$', views.deactivate_teamspeak3,
|
||||||
name='deactivate'),
|
name='deactivate'),
|
||||||
url(r'reset_perm/$', views.reset_teamspeak3_perm,
|
url(r'^reset_perm/$', views.reset_teamspeak3_perm,
|
||||||
name='reset_perm'),
|
name='reset_perm'),
|
||||||
|
|
||||||
# Teamspeak Urls
|
# Teamspeak Urls
|
||||||
url(r'verify/$', views.verify_teamspeak3, name='verify'),
|
url(r'^verify/$', views.verify_teamspeak3, name='verify'),
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class TS3Proto:
|
|||||||
|
|
||||||
def connect(self, ip, port):
|
def connect(self, ip, port):
|
||||||
try:
|
try:
|
||||||
self._conn = telnetlib.Telnet(host=ip, port=port)
|
self._conn = telnetlib.Telnet(host=ip, port=port, timeout=5)
|
||||||
self._connected = True
|
self._connected = True
|
||||||
except:
|
except:
|
||||||
# raise ConnectionError(ip, port)
|
# raise ConnectionError(ip, port)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import logging
|
|||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import login_required, permission_required
|
from django.contrib.auth.decorators import login_required, permission_required
|
||||||
from django.shortcuts import render, redirect
|
from django.shortcuts import render, redirect
|
||||||
|
from django.conf import settings
|
||||||
from .manager import Teamspeak3Manager
|
from .manager import Teamspeak3Manager
|
||||||
from .forms import TeamspeakJoinForm
|
from .forms import TeamspeakJoinForm
|
||||||
from .models import Teamspeak3User
|
from .models import Teamspeak3User
|
||||||
@@ -20,7 +20,6 @@ def activate_teamspeak3(request):
|
|||||||
logger.debug("activate_teamspeak3 called by user %s" % request.user)
|
logger.debug("activate_teamspeak3 called by user %s" % request.user)
|
||||||
|
|
||||||
character = request.user.profile.main_character
|
character = request.user.profile.main_character
|
||||||
ticker = character.corporation_ticker
|
|
||||||
with Teamspeak3Manager() as ts3man:
|
with Teamspeak3Manager() as ts3man:
|
||||||
logger.debug("Adding TS3 user for user %s with main character %s" % (request.user, character))
|
logger.debug("Adding TS3 user for user %s with main character %s" % (request.user, character))
|
||||||
result = ts3man.add_user(Teamspeak3Tasks.get_username(request.user))
|
result = ts3man.add_user(Teamspeak3Tasks.get_username(request.user))
|
||||||
@@ -56,6 +55,7 @@ def verify_teamspeak3(request):
|
|||||||
'form': form,
|
'form': form,
|
||||||
'authinfo': {'teamspeak3_uid': request.user.teamspeak3.uid,
|
'authinfo': {'teamspeak3_uid': request.user.teamspeak3.uid,
|
||||||
'teamspeak3_perm_key': request.user.teamspeak3.perm_key},
|
'teamspeak3_perm_key': request.user.teamspeak3.perm_key},
|
||||||
|
'public_url': settings.TEAMSPEAK3_PUBLIC_URL,
|
||||||
}
|
}
|
||||||
return render(request, 'services/teamspeak3/teamspeakjoin.html', context=context)
|
return render(request, 'services/teamspeak3/teamspeakjoin.html', context=context)
|
||||||
|
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ def pre_delete_user(sender, instance, *args, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
@receiver(pre_save, sender=User)
|
@receiver(pre_save, sender=User)
|
||||||
def pre_save_user(sender, instance, *args, **kwargs):
|
def disable_services_on_inactive(sender, instance, *args, **kwargs):
|
||||||
logger.debug("Received pre_save from %s" % instance)
|
logger.debug("Received pre_save from %s" % instance)
|
||||||
# check if user is being marked active/inactive
|
# check if user is being marked active/inactive
|
||||||
if not instance.pk:
|
if not instance.pk:
|
||||||
@@ -154,3 +154,17 @@ def pre_save_user(sender, instance, *args, **kwargs):
|
|||||||
disable_user(instance)
|
disable_user(instance)
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(pre_save, sender=UserProfile)
|
||||||
|
def disable_services_on_no_main(sender, instance, *args, **kwargs):
|
||||||
|
if not instance.pk:
|
||||||
|
# new model being created
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
old_instance = UserProfile.objects.get(pk=instance.pk)
|
||||||
|
if old_instance.main_character and not instance.main_character:
|
||||||
|
logger.info("Disabling services due to loss of main character for user {0}".format(instance.user))
|
||||||
|
disable_user(instance.user)
|
||||||
|
except UserProfile.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|||||||
@@ -1,44 +1,44 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
import redis
|
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
|
from django.contrib.auth.models import User
|
||||||
from .hooks import ServicesHook
|
from .hooks import ServicesHook
|
||||||
|
from celery_once import QueueOnce as BaseTask, AlreadyQueued
|
||||||
|
from celery_once.helpers import now_unix
|
||||||
|
from django.core.cache import cache
|
||||||
|
|
||||||
REDIS_CLIENT = redis.Redis()
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
# http://loose-bits.com/2010/10/distributed-task-locking-in-celery.html
|
class QueueOnce(BaseTask):
|
||||||
def only_one(function=None, key="", timeout=None):
|
once = BaseTask.once
|
||||||
"""Enforce only one celery task at a time."""
|
once['graceful'] = True
|
||||||
|
|
||||||
def _dec(run_func):
|
|
||||||
"""Decorator."""
|
|
||||||
|
|
||||||
def _caller(*args, **kwargs):
|
class DjangoBackend:
|
||||||
"""Caller."""
|
def __init__(self, settings):
|
||||||
ret_value = None
|
pass
|
||||||
have_lock = False
|
|
||||||
lock = REDIS_CLIENT.lock(key, timeout=timeout)
|
|
||||||
try:
|
|
||||||
have_lock = lock.acquire(blocking=False)
|
|
||||||
if have_lock:
|
|
||||||
ret_value = run_func(*args, **kwargs)
|
|
||||||
finally:
|
|
||||||
if have_lock:
|
|
||||||
lock.release()
|
|
||||||
|
|
||||||
return ret_value
|
@staticmethod
|
||||||
|
def raise_or_lock(key, timeout):
|
||||||
|
now = now_unix()
|
||||||
|
result = cache.get(key)
|
||||||
|
if result:
|
||||||
|
remaining = int(result) - now
|
||||||
|
if remaining > 0:
|
||||||
|
raise AlreadyQueued(remaining)
|
||||||
|
else:
|
||||||
|
cache.set(key, now + timeout, timeout)
|
||||||
|
|
||||||
return _caller
|
@staticmethod
|
||||||
|
def clear_lock(key):
|
||||||
return _dec(function) if function is not None else _dec
|
return cache.delete(key)
|
||||||
|
|
||||||
|
|
||||||
@shared_task(bind=True)
|
@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))
|
logger.debug('Ensuring user {} has permissions for active services'.format(user))
|
||||||
# Iterate through services hooks and have them check the validity of the user
|
# Iterate through services hooks and have them check the validity of the user
|
||||||
for svc in ServicesHook.get_services():
|
for svc in ServicesHook.get_services():
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ class NameFormatterTestCase(TestCase):
|
|||||||
corporation_ticker='TIKK',
|
corporation_ticker='TIKK',
|
||||||
alliance_id='3456',
|
alliance_id='3456',
|
||||||
alliance_name='alliance name',
|
alliance_name='alliance name',
|
||||||
|
alliance_ticker='TIKR',
|
||||||
)
|
)
|
||||||
self.member.profile.main_character = self.char
|
self.member.profile.main_character = self.char
|
||||||
self.member.profile.save()
|
self.member.profile.save()
|
||||||
@@ -83,11 +84,15 @@ class NameFormatterTestCase(TestCase):
|
|||||||
self.assertIn('alliance_name', result)
|
self.assertIn('alliance_name', result)
|
||||||
self.assertEqual(result['alliance_name'], self.char.alliance_name)
|
self.assertEqual(result['alliance_name'], self.char.alliance_name)
|
||||||
self.assertIn('alliance_ticker', result)
|
self.assertIn('alliance_ticker', result)
|
||||||
self.assertEqual(result['alliance_ticker'], self.char.alliance.alliance_ticker)
|
self.assertEqual(result['alliance_ticker'], self.char.alliance_ticker)
|
||||||
self.assertIn('alliance_id', result)
|
self.assertIn('alliance_id', result)
|
||||||
self.assertEqual(result['alliance_id'], self.char.alliance_id)
|
self.assertEqual(result['alliance_id'], self.char.alliance_id)
|
||||||
self.assertIn('username', result)
|
self.assertIn('username', result)
|
||||||
self.assertEqual(result['username'], self.member.username)
|
self.assertEqual(result['username'], self.member.username)
|
||||||
|
self.assertIn('alliance_or_corp_name', result)
|
||||||
|
self.assertEqual(result['alliance_or_corp_name'], self.char.alliance_name)
|
||||||
|
self.assertIn('alliance_or_corp_ticker', result)
|
||||||
|
self.assertEqual(result['alliance_or_corp_ticker'], self.char.alliance_ticker)
|
||||||
|
|
||||||
def test_format_name(self):
|
def test_format_name(self):
|
||||||
config = NameFormatConfig.objects.create(
|
config = NameFormatConfig.objects.create(
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from allianceauth.authentication.models import State
|
|||||||
class ServicesSignalsTestCase(TestCase):
|
class ServicesSignalsTestCase(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.member = AuthUtils.create_user('auth_member', disconnect_signals=True)
|
self.member = AuthUtils.create_user('auth_member', disconnect_signals=True)
|
||||||
|
AuthUtils.add_main_character(self.member, 'Test', '1', '2', 'Test Corp', 'TEST')
|
||||||
self.none_user = AuthUtils.create_user('none_user', disconnect_signals=True)
|
self.none_user = AuthUtils.create_user('none_user', disconnect_signals=True)
|
||||||
|
|
||||||
@mock.patch('allianceauth.services.signals.transaction')
|
@mock.patch('allianceauth.services.signals.transaction')
|
||||||
@@ -67,6 +68,18 @@ class ServicesSignalsTestCase(TestCase):
|
|||||||
args, kwargs = disable_user.call_args
|
args, kwargs = disable_user.call_args
|
||||||
self.assertEqual(self.member, args[0])
|
self.assertEqual(self.member, args[0])
|
||||||
|
|
||||||
|
@mock.patch('allianceauth.services.signals.disable_user')
|
||||||
|
def test_disable_services_on_loss_of_main_character(self, disable_user):
|
||||||
|
"""
|
||||||
|
Test a user set inactive has disable_member called
|
||||||
|
"""
|
||||||
|
self.member.profile.main_character = None
|
||||||
|
self.member.profile.save() # Signal Trigger
|
||||||
|
|
||||||
|
self.assertTrue(disable_user.called)
|
||||||
|
args, kwargs = disable_user.call_args
|
||||||
|
self.assertEqual(self.member, args[0])
|
||||||
|
|
||||||
@mock.patch('allianceauth.services.signals.transaction')
|
@mock.patch('allianceauth.services.signals.transaction')
|
||||||
@mock.patch('allianceauth.services.signals.ServicesHook')
|
@mock.patch('allianceauth.services.signals.ServicesHook')
|
||||||
def test_m2m_changed_group_permissions(self, services_hook, transaction):
|
def test_m2m_changed_group_permissions(self, services_hook, transaction):
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class ServicesTasksTestCase(TestCase):
|
|||||||
|
|
||||||
services_hook.get_services.return_value = [svc]
|
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(services_hook.get_services.called)
|
||||||
self.assertTrue(svc.validate_user.called)
|
self.assertTrue(svc.validate_user.called)
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ urlpatterns = [
|
|||||||
name='mark_uncompleted'),
|
name='mark_uncompleted'),
|
||||||
url(r'^request/remove/', views.srp_request_remove,
|
url(r'^request/remove/', views.srp_request_remove,
|
||||||
name="request_remove"),
|
name="request_remove"),
|
||||||
url(r'request/approve/', views.srp_request_approve,
|
url(r'^request/approve/', views.srp_request_approve,
|
||||||
name='request_approve'),
|
name='request_approve'),
|
||||||
url(r'request/reject/', views.srp_request_reject,
|
url(r'^request/reject/', views.srp_request_reject,
|
||||||
name='request_reject'),
|
name='request_reject'),
|
||||||
url(r'^request/(\w+)/update', views.srp_request_update_amount,
|
url(r'^request/(\w+)/update', views.srp_request_update_amount,
|
||||||
name="request_update_amount"),
|
name="request_update_amount"),
|
||||||
|
|||||||
@@ -42,7 +42,6 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<form id="f-lang-select" class="navbar-form navbar-right" action="{% url 'set_language' %}" method="post">
|
<form id="f-lang-select" class="navbar-form navbar-right" action="{% url 'set_language' %}" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input name="next" type="hidden" value="{{ request.get_full_path|slice:'3:' }}"/>
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<select onchange="this.form.submit()" class="form-control" id="lang-select" name="language">
|
<select onchange="this.form.submit()" class="form-control" id="lang-select" name="language">
|
||||||
{% get_language_info_list for LANGUAGES as languages %}
|
{% get_language_info_list for LANGUAGES as languages %}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from allianceauth.eveonline.models import EveCharacter
|
|||||||
|
|
||||||
from allianceauth.services.signals import m2m_changed_group_permissions, m2m_changed_user_permissions, \
|
from allianceauth.services.signals import m2m_changed_group_permissions, m2m_changed_user_permissions, \
|
||||||
m2m_changed_state_permissions
|
m2m_changed_state_permissions
|
||||||
from allianceauth.services.signals import m2m_changed_user_groups, pre_save_user
|
from allianceauth.services.signals import m2m_changed_user_groups, disable_services_on_inactive
|
||||||
|
|
||||||
|
|
||||||
class AuthUtils:
|
class AuthUtils:
|
||||||
@@ -90,7 +90,7 @@ class AuthUtils:
|
|||||||
m2m_changed.disconnect(m2m_changed_group_permissions, sender=Group.permissions.through)
|
m2m_changed.disconnect(m2m_changed_group_permissions, sender=Group.permissions.through)
|
||||||
m2m_changed.disconnect(m2m_changed_user_permissions, sender=User.user_permissions.through)
|
m2m_changed.disconnect(m2m_changed_user_permissions, sender=User.user_permissions.through)
|
||||||
m2m_changed.disconnect(m2m_changed_state_permissions, sender=State.permissions.through)
|
m2m_changed.disconnect(m2m_changed_state_permissions, sender=State.permissions.through)
|
||||||
pre_save.disconnect(pre_save_user, sender=User)
|
pre_save.disconnect(disable_services_on_inactive, sender=User)
|
||||||
m2m_changed.disconnect(state_member_corporations_changed, sender=State.member_corporations.through)
|
m2m_changed.disconnect(state_member_corporations_changed, sender=State.member_corporations.through)
|
||||||
m2m_changed.disconnect(state_member_characters_changed, sender=State.member_characters.through)
|
m2m_changed.disconnect(state_member_characters_changed, sender=State.member_characters.through)
|
||||||
m2m_changed.disconnect(state_member_alliances_changed, sender=State.member_alliances.through)
|
m2m_changed.disconnect(state_member_alliances_changed, sender=State.member_alliances.through)
|
||||||
@@ -102,7 +102,7 @@ class AuthUtils:
|
|||||||
m2m_changed.connect(m2m_changed_group_permissions, sender=Group.permissions.through)
|
m2m_changed.connect(m2m_changed_group_permissions, sender=Group.permissions.through)
|
||||||
m2m_changed.connect(m2m_changed_user_permissions, sender=User.user_permissions.through)
|
m2m_changed.connect(m2m_changed_user_permissions, sender=User.user_permissions.through)
|
||||||
m2m_changed.connect(m2m_changed_state_permissions, sender=State.permissions.through)
|
m2m_changed.connect(m2m_changed_state_permissions, sender=State.permissions.through)
|
||||||
pre_save.connect(pre_save_user, sender=User)
|
pre_save.connect(disable_services_on_inactive, sender=User)
|
||||||
m2m_changed.connect(state_member_corporations_changed, sender=State.member_corporations.through)
|
m2m_changed.connect(state_member_corporations_changed, sender=State.member_corporations.through)
|
||||||
m2m_changed.connect(state_member_characters_changed, sender=State.member_characters.through)
|
m2m_changed.connect(state_member_characters_changed, sender=State.member_characters.through)
|
||||||
m2m_changed.connect(state_member_alliances_changed, sender=State.member_alliances.through)
|
m2m_changed.connect(state_member_alliances_changed, sender=State.member_alliances.through)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user