mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2025-07-10 21:10:17 +02:00
Add token management and restrict logins to mains only
This commit is contained in:
parent
dd1313a2a9
commit
f17c94a9e1
@ -2,6 +2,7 @@ import logging
|
|||||||
|
|
||||||
from django.contrib.auth.backends import ModelBackend
|
from django.contrib.auth.backends import ModelBackend
|
||||||
from django.contrib.auth.models import User, Permission
|
from django.contrib.auth.models import User, Permission
|
||||||
|
from django.contrib import messages
|
||||||
|
|
||||||
from .models import UserProfile, CharacterOwnership, OwnershipRecord
|
from .models import UserProfile, CharacterOwnership, OwnershipRecord
|
||||||
|
|
||||||
@ -37,7 +38,13 @@ class StateBackend(ModelBackend):
|
|||||||
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(f'Authenticating {ownership.user} by ownership of character {token.character_name}')
|
logger.debug(f'Authenticating {ownership.user} by ownership of character {token.character_name}')
|
||||||
|
if ownership.user.profile.main_character:
|
||||||
|
if ownership.user.profile.main_character.character_id == token.character_id:
|
||||||
return ownership.user
|
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:
|
else:
|
||||||
logger.debug(f'{token.character_name} has changed ownership. Creating new user account.')
|
logger.debug(f'{token.character_name} has changed ownership. Creating new user account.')
|
||||||
ownership.delete()
|
ownership.delete()
|
||||||
@ -57,11 +64,18 @@ class StateBackend(ModelBackend):
|
|||||||
if records.exists():
|
if records.exists():
|
||||||
# we've seen this character owner before. Re-attach to their old user account
|
# we've seen this character owner before. Re-attach to their old user account
|
||||||
user = records[0].user
|
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
|
token.user = user
|
||||||
co = CharacterOwnership.objects.create_by_token(token)
|
co = CharacterOwnership.objects.create_by_token(token)
|
||||||
logger.debug(f'Authenticating {user} by matching owner hash record of character {co.character}')
|
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
|
# set this as their main by default as they have none
|
||||||
user.profile.main_character = co.character
|
user.profile.main_character = co.character
|
||||||
user.profile.save()
|
user.profile.save()
|
||||||
return user
|
return user
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
{% extends "allianceauth/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block page_title %}{% translate "Dashboard" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1 class="page-header text-center">{% translate "Token Management" %}</h1>
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<table class="table table-aa" id="table_tokens" style="width:100%">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Scopes</th>
|
||||||
|
<th class="text-right">Actions</th>
|
||||||
|
<th>Character</th>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for t in tokens %}
|
||||||
|
<tr>
|
||||||
|
<td styl="white-space:initial;">{% for s in t.scopes.all %}<span class="label label-default">{{s.name}}</span> {% endfor %}</td>
|
||||||
|
<td class="text-right"><a href="{% url 'authentication:token_revoke' t.id %}" class="btn btn-danger"><i class="fas fa-trash"></i></a> <a href="{% url 'authentication:token_refresh' t.id %}" class="btn btn-success"><i class="fas fa-sync-alt"></i></a></td>
|
||||||
|
<td>{{t.character_name}}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% 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('<tr class="info"><td colspan="3">' + group + '</td></tr>');
|
||||||
|
last = group;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
{% endblock %}
|
@ -116,10 +116,17 @@ class TestAuthenticate(TestCase):
|
|||||||
user = StateBackend().authenticate(token=t)
|
user = StateBackend().authenticate(token=t)
|
||||||
self.assertEqual(user, self.user)
|
self.assertEqual(user, self.user)
|
||||||
|
|
||||||
|
""" Alt Login disabled
|
||||||
def test_authenticate_alt_character(self):
|
def test_authenticate_alt_character(self):
|
||||||
t = Token(character_id=self.alt_character.character_id, character_owner_hash='2')
|
t = Token(character_id=self.alt_character.character_id, character_owner_hash='2')
|
||||||
user = StateBackend().authenticate(token=t)
|
user = StateBackend().authenticate(token=t)
|
||||||
self.assertEqual(user, self.user)
|
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):
|
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')
|
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.username, 'Unclaimed_Character')
|
||||||
self.assertEqual(user.profile.main_character, self.unclaimed_character)
|
self.assertEqual(user.profile.main_character, self.unclaimed_character)
|
||||||
|
|
||||||
|
""" Alt Login disabled
|
||||||
def test_authenticate_character_record(self):
|
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')
|
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')
|
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.assertEqual(user, self.old_user)
|
||||||
self.assertTrue(CharacterOwnership.objects.filter(owner_hash='4', user=self.old_user).exists())
|
self.assertTrue(CharacterOwnership.objects.filter(owner_hash='4', user=self.old_user).exists())
|
||||||
self.assertTrue(user.profile.main_character)
|
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):
|
def test_iterate_username(self):
|
||||||
t = Token(character_id=self.unclaimed_character.character_id,
|
t = Token(character_id=self.unclaimed_character.character_id,
|
||||||
|
@ -22,5 +22,20 @@ urlpatterns = [
|
|||||||
views.add_character,
|
views.add_character,
|
||||||
name='add_character'
|
name='add_character'
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
'account/tokens/manage/',
|
||||||
|
views.token_management,
|
||||||
|
name='token_management'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'account/tokens/revoke/<int:token_id>',
|
||||||
|
views.token_revoke,
|
||||||
|
name='token_revoke'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'account/tokens/refresh/<int:token_id>',
|
||||||
|
views.token_refresh,
|
||||||
|
name='token_refresh'
|
||||||
|
),
|
||||||
path('dashboard/', views.dashboard, name='dashboard'),
|
path('dashboard/', views.dashboard, name='dashboard'),
|
||||||
]
|
]
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
|
from glob import escape
|
||||||
import logging
|
import logging
|
||||||
|
from symbol import except_clause
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
@ -61,6 +63,44 @@ def dashboard(request):
|
|||||||
}
|
}
|
||||||
return render(request, 'authentication/dashboard.html', context)
|
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
|
@login_required
|
||||||
@token_required(scopes=settings.LOGIN_TOKEN_SCOPES)
|
@token_required(scopes=settings.LOGIN_TOKEN_SCOPES)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user