diff --git a/allianceauth/authentication/backends.py b/allianceauth/authentication/backends.py
index 526950d9..94ac066b 100644
--- a/allianceauth/authentication/backends.py
+++ b/allianceauth/authentication/backends.py
@@ -2,6 +2,7 @@ import logging
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import User, Permission
+from django.contrib import messages
from .models import UserProfile, CharacterOwnership, OwnershipRecord
@@ -37,7 +38,13 @@ class StateBackend(ModelBackend):
ownership = CharacterOwnership.objects.get(character__character_id=token.character_id)
if ownership.owner_hash == token.character_owner_hash:
logger.debug(f'Authenticating {ownership.user} by ownership of character {token.character_name}')
- return ownership.user
+ if ownership.user.profile.main_character:
+ if ownership.user.profile.main_character.character_id == token.character_id:
+ return ownership.user
+ else: ## this is an alt, enforce main only.
+ if request:
+ messages.error("Unable to authenticate with this Character, Please log in with the main character associated with this account.")
+ return None
else:
logger.debug(f'{token.character_name} has changed ownership. Creating new user account.')
ownership.delete()
@@ -57,13 +64,20 @@ class StateBackend(ModelBackend):
if records.exists():
# we've seen this character owner before. Re-attach to their old user account
user = records[0].user
+ if user.profile.main_character:
+ if ownership.user.profile.main_character.character_id != token.character_id:
+ ## this is an alt, enforce main only due to trust issues in SSO.
+ if request:
+ messages.error("Unable to authenticate with this Character, Please log in with the main character associated with this account. Then add this character from the dashboard.")
+ return None
+
token.user = user
co = CharacterOwnership.objects.create_by_token(token)
logger.debug(f'Authenticating {user} by matching owner hash record of character {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()
+
+ # set this as their main by default as they have none
+ user.profile.main_character = co.character
+ user.profile.save()
return user
logger.debug(f'Unable to authenticate character {token.character_name}. Creating new user.')
return self.create_user(token)
diff --git a/allianceauth/authentication/templates/authentication/tokens.html b/allianceauth/authentication/templates/authentication/tokens.html
new file mode 100644
index 00000000..e90be72b
--- /dev/null
+++ b/allianceauth/authentication/templates/authentication/tokens.html
@@ -0,0 +1,61 @@
+{% extends "allianceauth/base.html" %}
+{% load i18n %}
+
+{% block page_title %}{% translate "Dashboard" %}{% endblock %}
+
+{% block content %}
+
+
+
+
+
+ Scopes |
+ Actions |
+ Character |
+
+
+
+
+ {% for t in tokens %}
+
+ {% for s in t.scopes.all %}{{s.name}} {% endfor %} |
+ |
+ {{t.character_name}} |
+
+ {% endfor %}
+
+
+
+{% endblock %}
+
+{% block extra_javascript %}
+ {% include 'bundles/datatables-js.html' %}
+{% endblock %}
+
+{% block extra_css %}
+ {% include 'bundles/datatables-css.html' %}
+{% endblock %}
+
+{% block extra_script %}
+ $(document).ready(function(){
+ let grp = 2;
+ var table = $('#table_tokens').DataTable({
+ "columnDefs": [{ orderable: false, targets: [0,1] },{ "visible": false, "targets": grp }],
+ "order": [[grp, 'asc']],
+ "drawCallback": function (settings) {
+ var api = this.api();
+ var rows = api.rows({ page: 'current' }).nodes();
+ var last = null;
+ api.column(grp, { page: 'current' })
+ .data()
+ .each(function (group, i) {
+ if (last !== group) {
+ $(rows).eq(i).before('' + group + ' |
');
+ last = group;
+ }
+ });
+ }
+ });
+
+ });
+{% endblock %}
diff --git a/allianceauth/authentication/tests/test_backend.py b/allianceauth/authentication/tests/test_backend.py
index ed984521..92936355 100644
--- a/allianceauth/authentication/tests/test_backend.py
+++ b/allianceauth/authentication/tests/test_backend.py
@@ -116,10 +116,17 @@ class TestAuthenticate(TestCase):
user = StateBackend().authenticate(token=t)
self.assertEqual(user, self.user)
+ """ Alt Login disabled
def test_authenticate_alt_character(self):
t = Token(character_id=self.alt_character.character_id, character_owner_hash='2')
user = StateBackend().authenticate(token=t)
self.assertEqual(user, self.user)
+ """
+
+ def test_authenticate_alt_character_fail(self):
+ t = Token(character_id=self.alt_character.character_id, character_owner_hash='2')
+ user = StateBackend().authenticate(token=t)
+ self.assertEqual(user, None)
def test_authenticate_unclaimed_character(self):
t = Token(character_id=self.unclaimed_character.character_id, character_name=self.unclaimed_character.character_name, character_owner_hash='3')
@@ -128,6 +135,7 @@ class TestAuthenticate(TestCase):
self.assertEqual(user.username, 'Unclaimed_Character')
self.assertEqual(user.profile.main_character, self.unclaimed_character)
+ """ Alt Login disabled
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')
OwnershipRecord.objects.create(user=self.old_user, character=self.unclaimed_character, owner_hash='4')
@@ -135,6 +143,15 @@ class TestAuthenticate(TestCase):
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_authenticate_character_record_fails(self):
+ t = Token(character_id=self.unclaimed_character.character_id, character_name=self.unclaimed_character.character_name, character_owner_hash='4')
+ OwnershipRecord.objects.create(user=self.old_user, character=self.unclaimed_character, owner_hash='4')
+ user = StateBackend().authenticate(token=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):
t = Token(character_id=self.unclaimed_character.character_id,
diff --git a/allianceauth/authentication/urls.py b/allianceauth/authentication/urls.py
index adef3e5f..03a0da18 100644
--- a/allianceauth/authentication/urls.py
+++ b/allianceauth/authentication/urls.py
@@ -22,5 +22,20 @@ urlpatterns = [
views.add_character,
name='add_character'
),
+ path(
+ 'account/tokens/manage/',
+ views.token_management,
+ name='token_management'
+ ),
+ path(
+ 'account/tokens/revoke/',
+ views.token_revoke,
+ name='token_revoke'
+ ),
+ path(
+ 'account/tokens/refresh/',
+ views.token_refresh,
+ name='token_refresh'
+ ),
path('dashboard/', views.dashboard, name='dashboard'),
]
diff --git a/allianceauth/authentication/views.py b/allianceauth/authentication/views.py
index 779f9a1c..b8c18a64 100644
--- a/allianceauth/authentication/views.py
+++ b/allianceauth/authentication/views.py
@@ -1,4 +1,6 @@
+from glob import escape
import logging
+from symbol import except_clause
from django.conf import settings
from django.contrib import messages
@@ -61,6 +63,44 @@ def dashboard(request):
}
return render(request, 'authentication/dashboard.html', context)
+@login_required
+def token_management(request):
+ tokens = request.user.token_set.all()
+
+ context = {
+ 'tokens': tokens
+ }
+ return render(request, 'authentication/tokens.html', context)
+
+@login_required
+def token_revoke(request, token_id=None):
+ try:
+ token = Token.objects.get(id=token_id)
+ if request.user == token.user:
+ token.delete()
+ messages.success(request, "Token Deleted.")
+ else:
+ messages.error(request, "This token does not belong to you.")
+ except Token.DoesNotExist:
+ messages.warning(request, "Token does not exist")
+ return redirect('authentication:token_management')
+
+@login_required
+def token_refresh(request, token_id=None):
+ try:
+ token = Token.objects.get(id=token_id)
+ if request.user == token.user:
+ try:
+ token.refresh()
+ messages.success(request, "Token refreshed.")
+ except Exception as e:
+ messages.warning(request, f"Failed to refresh token. {e}")
+ else:
+ messages.error(request, "This token does not belong to you.")
+ except Token.DoesNotExist:
+ messages.warning(request, "Token does not exist")
+ return redirect('authentication:token_management')
+
@login_required
@token_required(scopes=settings.LOGIN_TOKEN_SCOPES)