diff --git a/alliance_auth/settings.py.example b/alliance_auth/settings.py.example
index 42b55319..271eace0 100644
--- a/alliance_auth/settings.py.example
+++ b/alliance_auth/settings.py.example
@@ -344,6 +344,8 @@ ALLIANCE_NAME = os.environ.get('AA_ALLIANCE_NAME', '')
# BLUE_API_ACCOUNT - Require API to be for Account and not character restricted
# REJECT_OLD_APIS - Require each submitted API be newer than the latest submitted API
# REJECT_OLD_APIS_MARGIN - Margin from latest submitted API ID within which a newly submitted API is still accepted
+# API_SSO_VALIDATION - Require users to prove ownership of newly entered API keys via SSO
+# Requires SSO to be configured.
#######################
MEMBER_API_MASK = os.environ.get('AA_MEMBER_API_MASK', 268435455)
MEMBER_API_ACCOUNT = 'True' == os.environ.get('AA_MEMBER_API_ACCOUNT', 'True')
@@ -351,6 +353,7 @@ BLUE_API_MASK = os.environ.get('AA_BLUE_API_MASK', 8388608)
BLUE_API_ACCOUNT = 'True' == os.environ.get('AA_BLUE_API_ACCOUNT', 'False')
REJECT_OLD_APIS = 'True' == os.environ.get('AA_REJECT_OLD_APIS', 'False')
REJECT_OLD_APIS_MARGIN = os.environ.get('AA_REJECT_OLD_APIS_MARGIN', 50)
+API_SSO_VALIDATION = 'True' == os.environ.get('AA_API_SSO_VALIDATION', 'False')
#####################
# Alliance Market
diff --git a/alliance_auth/urls.py b/alliance_auth/urls.py
index 26d204ea..29613fc5 100755
--- a/alliance_auth/urls.py
+++ b/alliance_auth/urls.py
@@ -39,6 +39,7 @@ urlpatterns = [
# Eve Online
url(r'^main_character_change/(\w+)/$', eveonline.views.main_character_change,
name='auth_main_character_change'),
+ url(r'^api_verify_owner/(\w+)/$', eveonline.views.api_sso_validate, name='auth_api_sso'),
# Forum Service Control
url(r'^activate_forum/$', services.views.activate_forum, name='auth_activate_forum'),
diff --git a/authentication/admin.py b/authentication/admin.py
index dc41e4f8..a700af4e 100644
--- a/authentication/admin.py
+++ b/authentication/admin.py
@@ -99,7 +99,7 @@ class AuthServicesInfoManager(admin.ModelAdmin):
def sync_discourse(self, request, queryset):
count = 0
for a in queryset:
- if a.discourse_username != "":
+ if a.discourse_enabled:
update_discourse_groups.delay(a.user.pk)
count += 1
self.message_user(request, "%s discourse accounts queued for group sync." % count)
@@ -137,7 +137,6 @@ class AuthServicesInfoManager(admin.ModelAdmin):
'mumble_username',
'teamspeak3_uid',
'discord_uid',
- 'discourse_username',
'ips4_username',
'smf_username',
'market_username',
diff --git a/eveonline/forms.py b/eveonline/forms.py
index 7588fdbc..9572f478 100644
--- a/eveonline/forms.py
+++ b/eveonline/forms.py
@@ -4,6 +4,7 @@ from django.conf import settings
from services.managers.eve_api_manager import EveApiManager
from eveonline.managers import EveManager
+from eveonline.models import EveApiKeyPair
import evelink
import logging
@@ -31,7 +32,9 @@ class UpdateKeyForm(forms.Form):
if EveManager.check_if_api_key_pair_exist(self.cleaned_data['api_id']):
logger.debug("UpdateKeyForm failed cleaning as API id %s already exists." % self.cleaned_data['api_id'])
- raise forms.ValidationError('API key already exist')
+ if EveApiKeyPair.objects.get(api_id=self.cleaned_data['api_id']).user:
+ # allow orphaned APIs to proceed to SSO validation upon re-entry
+ raise forms.ValidationError('API key already exist')
if settings.REJECT_OLD_APIS and not EveManager.check_if_api_key_pair_is_new(
self.cleaned_data['api_id'],
settings.REJECT_OLD_APIS_MARGIN):
diff --git a/eveonline/migrations/0003_auto_20161026_0149.py b/eveonline/migrations/0003_auto_20161026_0149.py
new file mode 100644
index 00000000..beffba20
--- /dev/null
+++ b/eveonline/migrations/0003_auto_20161026_0149.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.2 on 2016-10-26 01:49
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('eveonline', '0002_remove_eveapikeypair_error_count'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='eveapikeypair',
+ name='user',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
+ ),
+ migrations.AlterField(
+ model_name='evecharacter',
+ name='user',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
+ ),
+ ]
diff --git a/eveonline/models.py b/eveonline/models.py
index f3926a6f..77684100 100644
--- a/eveonline/models.py
+++ b/eveonline/models.py
@@ -14,7 +14,7 @@ class EveCharacter(models.Model):
alliance_id = models.CharField(max_length=254)
alliance_name = models.CharField(max_length=254)
api_id = models.CharField(max_length=254)
- user = models.ForeignKey(User)
+ user = models.ForeignKey(User, blank=True, null=True)
def __str__(self):
return self.character_name
@@ -24,10 +24,10 @@ class EveCharacter(models.Model):
class EveApiKeyPair(models.Model):
api_id = models.CharField(max_length=254)
api_key = models.CharField(max_length=254)
- user = models.ForeignKey(User)
+ user = models.ForeignKey(User, blank=True, null=True)
def __str__(self):
- return self.user.username + " - ApiKeyPair"
+ return self.api_id
@python_2_unicode_compatible
diff --git a/eveonline/views.py b/eveonline/views.py
index d224d3c1..1e8cd63d 100755
--- a/eveonline/views.py
+++ b/eveonline/views.py
@@ -1,5 +1,5 @@
from __future__ import unicode_literals
-from django.shortcuts import render, redirect
+from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.contrib import messages
@@ -7,11 +7,13 @@ from eveonline.forms import UpdateKeyForm
from eveonline.managers import EveManager
from authentication.managers import AuthServicesInfoManager
from services.managers.eve_api_manager import EveApiManager
-from eveonline.models import EveApiKeyPair
+from eveonline.models import EveApiKeyPair, EveCharacter
from authentication.models import AuthServicesInfo
from authentication.tasks import set_state
from eveonline.tasks import refresh_api
+from eve_sso.decorators import token_required
+from django.conf import settings
import logging
logger = logging.getLogger(__name__)
@@ -24,20 +26,39 @@ def add_api_key(request):
form = UpdateKeyForm(request.user, request.POST)
logger.debug("Request type POST with form valid: %s" % form.is_valid())
if form.is_valid():
- EveManager.create_api_keypair(form.cleaned_data['api_id'],
- form.cleaned_data['api_key'],
- request.user)
-
+ if EveApiKeyPair.objects.filter(api_id=form.cleaned_data['api_id'],
+ api_key=form.cleaned_data['api_key']).exists():
+ # allow orphaned keys to proceed to SSO validation upon re-entry
+ api_key = EveApiKeyPair.objects.get(api_id=form.cleaned_data['api_id'],
+ api_key=form.cleaned_data['api_key'])
+ elif EveApiKeyPair.objects.filter(api_id=form.cleaned_data['api_id']).exists():
+ logger.warn('API %s re-added with different vcode.' % form.cleaned_data['api_id'])
+ EveApiKeyPair.objects.filter(api_id=form.cleaned_data['api_id']).delete()
+ api_key = EveApiKeyPair.objects.create(api_id=form.cleaned_data['api_id'],
+ api_key=form.cleaned_data['api_key'])
+ else:
+ api_key = EveApiKeyPair.objects.create(api_id=form.cleaned_data['api_id'],
+ api_key=form.cleaned_data['api_key'])
+ owner = None
+ if not settings.API_SSO_VALIDATION:
+ # set API and character owners if SSO validation not requested
+ api_key.user = request.user
+ api_key.save()
+ owner = request.user
# Grab characters associated with the key pair
characters = EveApiManager.get_characters_from_api(form.cleaned_data['api_id'],
form.cleaned_data['api_key'])
- EveManager.create_characters_from_list(characters, request.user, form.cleaned_data['api_id'])
+ EveManager.create_characters_from_list(characters, owner, form.cleaned_data['api_id'])
logger.info("Successfully processed api add form for user %s" % request.user)
- messages.success(request, 'Added API key %s to your account.' % form.cleaned_data['api_id'])
- auth = AuthServicesInfo.objects.get_or_create(user=request.user)[0]
- if not auth.main_char_id:
- messages.warning(request, 'Please select a main character.')
- return redirect("/api_key_management/")
+ if not settings.API_SSO_VALIDATION:
+ messages.success(request, 'Added API key %s to your account.' % form.cleaned_data['api_id'])
+ auth = AuthServicesInfo.objects.get_or_create(user=request.user)[0]
+ if not auth.main_char_id:
+ return redirect('auth_characters')
+ return redirect("/api_key_management/")
+ else:
+ logger.debug('Requesting SSO validation of API %s by user %s' % (api_key.api_id, request.user))
+ return render(request, 'registered/apisso.html', context={'api':api_key})
else:
logger.debug("Form invalid: returning to form.")
else:
@@ -47,6 +68,32 @@ def add_api_key(request):
return render(request, 'registered/addapikey.html', context=context)
+@login_required
+@token_required(new=True)
+def api_sso_validate(request, tokens, api_id):
+ logger.debug('api_sso_validate called by user %s for api %s' % (request.user, api_id))
+ api = get_object_or_404(EveApiKeyPair, api_id=api_id)
+ if api.user:
+ logger.warning('User %s attempting to take ownership of api %s from %s' % (request.user, api_id, api.user))
+ messages.warning(request, 'API %s already claimed by user %s' % (api_id, api.user))
+ return redirect('auth_api_key_management')
+ token = tokens[0]
+ logger.debug('API %s has no owner. Checking if token for %s matches.' % (api_id, token.character_name))
+ characters = EveApiManager.get_characters_from_api(api.api_id, api.api_key).result
+ if token.character_id in characters:
+ api.user = request.user
+ api.save()
+ EveCharacter.objects.filter(character_id__in=characters).update(user=request.user, api_id=api_id)
+ messages.success(request, 'Confirmed ownership of API %s' % api.api_id)
+ auth, c = AuthServicesInfo.objects.get_or_create(user=request.user)
+ if not auth.main_char_id:
+ return redirect('auth_characters')
+ return redirect('auth_api_key_management')
+ else:
+ messages.warning(request, '%s not found on API %s. Please SSO as a character on the API.' % (token.character_name, api.api_id))
+ return render(request, 'registered/apisso.html', context={'api':api})
+
+
@login_required
def api_key_management_view(request):
logger.debug("api_key_management_view called by user %s" % request.user)
@@ -59,21 +106,17 @@ def api_key_management_view(request):
def api_key_removal(request, api_id):
logger.debug("api_key_removal called by user %s for api id %s" % (request.user, api_id))
authinfo = AuthServicesInfo.objects.get_or_create(user=request.user)[0]
- # Check if our users main id is in the to be deleted characters
- characters = EveManager.get_characters_by_owner_id(request.user.id)
- if characters is not None:
- for character in characters:
- if character.character_id == authinfo.main_char_id:
- if character.api_id == api_id:
- messages.warning(request,
- 'You have deleted your main character. Please select a new main character.')
- set_state(request.user)
-
EveManager.delete_api_key_pair(api_id, request.user.id)
EveManager.delete_characters_by_api_id(api_id, request.user.id)
messages.success(request, 'Deleted API key %s' % api_id)
logger.info("Succesfully processed api delete request by user %s for api %s" % (request.user, api_id))
- return redirect("auth_api_key_management")
+ if EveCharacter.objects.filter(character_id=authinfo.main_char_id).exists():
+ return redirect("auth_api_key_management")
+ else:
+ authinfo.main_char_id = None
+ authinfo.save()
+ set_state(request.user)
+ return redirect("auth_characters")
@login_required
@@ -89,8 +132,8 @@ def main_character_change(request, char_id):
logger.debug("main_character_change called by user %s for character id %s" % (request.user, char_id))
if EveManager.check_if_character_owned_by_user(char_id, request.user):
AuthServicesInfoManager.update_main_char_id(char_id, request.user)
- set_state(request.user)
messages.success(request, 'Changed main character ID to %s' % char_id)
+ set_state(request.user)
return redirect("auth_characters")
messages.error(request, 'Failed to change main character - selected character is not owned by your account.')
return redirect("auth_characters")
diff --git a/services/managers/discourse_manager.py b/services/managers/discourse_manager.py
index 830fbaf9..1953bf57 100644
--- a/services/managers/discourse_manager.py
+++ b/services/managers/discourse_manager.py
@@ -343,7 +343,10 @@ class DiscourseManager:
@staticmethod
def _sanitize_groupname(name):
name = name.strip(' _')
- return re.sub('[^\w]', '', name)
+ name = re.sub('[^\w]', '', name)
+ if len(name) < 3:
+ name = name + "".join('_' for i in range(3-len(name)))
+ return name[:20]
@staticmethod
def update_groups(user):
diff --git a/stock/templates/registered/apikeymanagment.html b/stock/templates/registered/apikeymanagment.html
index 6e7c963d..c6de2c29 100644
--- a/stock/templates/registered/apikeymanagment.html
+++ b/stock/templates/registered/apikeymanagment.html
@@ -9,38 +9,36 @@
{% block extra_css %}{% endblock extra_css %}
{% block content %}
-
+
- {% if apikeypairs %}
- {% else %}
-
{% trans "No api keys found" %}
- {% endif %}
-
-
- {% trans "API ID" %} |
- {% trans "API Key" %} |
- {% trans "Action" %} |
-
- {% for pair in apikeypairs %}
+ {% if apikeypairs %}
+
- {{ pair.api_id }} |
- {{ pair.api_key }} |
-
-
-
-
-
-
-
- |
+ {% trans "API ID" %} |
+ {% trans "Action" %} |
- {% endfor %}
-
+ {% for pair in apikeypairs %}
+
+ {{ pair.api_id }} |
+
+
+
+
+
+
+
+ |
+
+ {% endfor %}
+
+ {% else %}
+
{% trans "No api keys found" %}
+ {% endif %}
diff --git a/stock/templates/registered/apisso.html b/stock/templates/registered/apisso.html
new file mode 100644
index 00000000..e9bb4291
--- /dev/null
+++ b/stock/templates/registered/apisso.html
@@ -0,0 +1,21 @@
+{% extends 'public/base.html' %}
+{% load staticfiles %}
+{% block title %}Verify API Ownership{% endblock %}
+{% block page_title%}Verify API Ownership{% endblock %}
+{% block content %}
+
+
+
+
Please authenticate as a character on API {{ api.api_id }} to prove ownership.
+
+
+
+
+
+
+
+
+
+
+{% endblock %}
diff --git a/stock/templates/registered/fatlinkpersonalmonthlystatisticsview.html b/stock/templates/registered/fatlinkpersonalmonthlystatisticsview.html
index fac9a02c..c73da6de 100644
--- a/stock/templates/registered/fatlinkpersonalmonthlystatisticsview.html
+++ b/stock/templates/registered/fatlinkpersonalmonthlystatisticsview.html
@@ -17,7 +17,7 @@
{% endif %}
{% blocktrans %}{{ user }} has collected {{ n_fats }} links this month.{% endblocktrans %}
-
+
{% trans "Ship" %} |
{% trans "Times used" %} |
@@ -31,7 +31,8 @@
{% if created_fats %}
{% blocktrans %}{{ user }} has created {{ n_created_fats }} links this month.{% endblocktrans %}
-
+ {% if created_fats %}
+
{% trans "Name" %} |
{% trans "Creator" %} |
@@ -58,6 +59,7 @@
{% endif %}
+ {% endif %}
diff --git a/stock/templates/registered/fatlinkpersonalstatisticsview.html b/stock/templates/registered/fatlinkpersonalstatisticsview.html
index 53bae5b9..a85b5f2b 100644
--- a/stock/templates/registered/fatlinkpersonalstatisticsview.html
+++ b/stock/templates/registered/fatlinkpersonalstatisticsview.html
@@ -16,7 +16,8 @@
{% endif %}
-
+
+
{% trans "Month" %} |
{% trans "Fats" %} |
@@ -32,6 +33,7 @@
{% endfor %}
+
diff --git a/stock/templates/registered/fatlinkstatisticsview.html b/stock/templates/registered/fatlinkstatisticsview.html
index 9fe2349d..bb1aa7cc 100644
--- a/stock/templates/registered/fatlinkstatisticsview.html
+++ b/stock/templates/registered/fatlinkstatisticsview.html
@@ -16,7 +16,8 @@
{% endif %}
-
+ {% if fatStats %}
+
|
{% trans "Ticker" %} |
@@ -38,6 +39,7 @@
{% endfor %}
+ {% endif %}
diff --git a/stock/templates/registered/fatlinkview.html b/stock/templates/registered/fatlinkview.html
index bd201cc0..69e75f0f 100644
--- a/stock/templates/registered/fatlinkview.html
+++ b/stock/templates/registered/fatlinkview.html
@@ -22,7 +22,8 @@
-
+ {% if fats %}
+
{% trans "fatname" %} |
{% trans "Character" %} |
@@ -44,6 +45,9 @@
{% endfor %}
+ {% else %}
+ No fleet activity on record.
+ {% endif %}
{% if perms.auth.fleetactivitytracking%}
-
+ {% if fatlinks %}
+
{% trans "Name" %} |
{% trans "Creator" %} |
@@ -89,6 +94,9 @@
{% endfor %}
+ {% else %}
+ No created fatlinks on record.
+ {% endif %}
{% endif %}
diff --git a/stock/templates/registered/groupmanagement.html b/stock/templates/registered/groupmanagement.html
index c2b3ebd5..9f48722d 100644
--- a/stock/templates/registered/groupmanagement.html
+++ b/stock/templates/registered/groupmanagement.html
@@ -10,59 +10,73 @@
{% block content %}
-
-
-
-
- {% trans "RequestID" %} |
- {% trans "CharacterName" %} |
- {% trans "GroupName" %} |
- {% trans "Action" %} |
-
-
- {% for acceptrequest in acceptrequests %}
-
- {{ acceptrequest.id }} |
- {{ acceptrequest.main_char.character_name }} |
- {{ acceptrequest.group.name }} |
-
-
- {% trans "Accept" %}
-
-
-
- {% trans "Reject" %}
-
- |
-
- {% endfor %}
-
-
-
-
- {% trans "RequestID" %} |
- {% trans "CharacterName" %} |
- {% trans "GroupName" %} |
- {% trans "Action" %} |
-
-
- {% for leaverequest in leaverequests %}
-
- {{ leaverequest.id }} |
- {{ leaverequest.main_char.character_name }} |
- {{ leaverequest.group.name }} |
-
-
- {% trans "Accept" %}
-
-
-
- {% trans "Reject" %}
-
- |
-
- {% endfor %}
-
+
+
+
+
+ {% if acceptrequests %}
+
+
+ {% trans "RequestID" %} |
+ {% trans "CharacterName" %} |
+ {% trans "GroupName" %} |
+ {% trans "Action" %} |
+
+ {% for acceptrequest in acceptrequests %}
+
+ {{ acceptrequest.id }} |
+ {{ acceptrequest.main_char.character_name }} |
+ {{ acceptrequest.group.name }} |
+
+
+ {% trans "Accept" %}
+
+
+ {% trans "Reject" %}
+
+ |
+
+ {% endfor %}
+
+ {% else %}
+
No group add requests.
+ {% endif %}
+
+
+
+
+ {% if leaverequests %}
+
+
+ {% trans "RequestID" %} |
+ {% trans "CharacterName" %} |
+ {% trans "GroupName" %} |
+ {% trans "Action" %} |
+
+ {% for leaverequest in leaverequests %}
+
+ {{ leaverequest.id }} |
+ {{ leaverequest.main_char.character_name }} |
+ {{ leaverequest.group.name }} |
+
+
+ {% trans "Accept" %}
+
+
+ {% trans "Reject" %}
+
+ |
+
+ {% endfor %}
+
+ {% else %}
+
No group leave requests.
+ {% endif %}
+
+
+
-
{% endblock content %}
diff --git a/stock/templates/registered/groups.html b/stock/templates/registered/groups.html
index ca5ec16e..2581544a 100644
--- a/stock/templates/registered/groups.html
+++ b/stock/templates/registered/groups.html
@@ -11,9 +11,9 @@
{% if STATE == MEMBER_STATE or user.is_superuser %}
-
+ {% if pairs %}
+
- {% trans "GroupID" %} |
{% trans "GroupName" %} |
{% trans "GroupDesc" %} |
{% trans "Action" %} |
@@ -21,7 +21,6 @@
{% for pair in pairs %}
- {{ pair.0.id }} |
{{ pair.0.name }} |
{{ pair.1.description }} |
@@ -48,6 +47,9 @@
|
{% endfor %}
+ {% else %}
+ No groups available.
+ {% endif %}
{% else %}
{% if IS_CORP %}
{% trans "You are not in the corporation." %}
diff --git a/stock/templates/registered/hrapplicationmanagement.html b/stock/templates/registered/hrapplicationmanagement.html
index 660b12eb..f3967698 100755
--- a/stock/templates/registered/hrapplicationmanagement.html
+++ b/stock/templates/registered/hrapplicationmanagement.html
@@ -10,7 +10,7 @@
{% block content %}
- {% if not STATE == MEMBER_STATE and not user.is_superuser %}
+ {% if not STATE == MEMBER_STATE %}
-
+ {% if personal_apps %}
+
{% trans "Username" %} |
{% trans "Corporation" %}
@@ -54,6 +55,7 @@
|
{% endfor %}
+ {% endif %}
{% endif %}
{% if perms.auth.human_resources %}