SeAT service cleanup (#796)

Manager function tidyup
Hopefully improved the key sync function, at least it should be easier to follow whats happening now.
Remove partial logging of unhashed passwords
Added user feedback
This commit is contained in:
Basraah 2017-05-05 00:31:57 +10:00 committed by Adarnof
parent 4556a0e740
commit aec013b93c
2 changed files with 133 additions and 91 deletions

View File

@ -2,9 +2,11 @@ from __future__ import unicode_literals
import random
import string
import requests
import hashlib
from eveonline.managers import EveManager
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.core.cache import cache
from six import iteritems
@ -20,7 +22,7 @@ class SeatManager:
RESPONSE_OK = 'ok'
@staticmethod
def __santatize_username(username):
def __sanitize_username(username):
sanatized = username.replace(" ", "_")
return sanatized.lower()
@ -33,7 +35,7 @@ class SeatManager:
return cls.RESPONSE_OK in response
@staticmethod
def exec_request(endpoint, func, **kwargs):
def exec_request(endpoint, func, raise_for_status=False, **kwargs):
""" Send an https api request """
try:
endpoint = '{0}/api/v1/{1}'.format(settings.SEAT_URL, endpoint)
@ -43,22 +45,24 @@ class SeatManager:
ret = getattr(requests, func)(endpoint, headers=headers, data=kwargs)
ret.raise_for_status()
return ret.json()
except:
except requests.HTTPError as e:
if raise_for_status:
raise e
logger.exception("Error encountered while performing API request to SeAT with url {}".format(endpoint))
return {}
@classmethod
def add_user(cls, username, email):
""" Add user to service """
sanatized = str(SeatManager.__santatize_username(username))
logger.debug("Adding user to SeAT with username %s" % sanatized)
password = SeatManager.__generate_random_pass()
ret = SeatManager.exec_request('user', 'post', username=sanatized, email=str(email), password=password)
sanitized = str(cls.__sanitize_username(username))
logger.debug("Adding user to SeAT with username %s" % sanitized)
password = cls.__generate_random_pass()
ret = cls.exec_request('user', 'post', username=sanitized, email=str(email), password=password)
logger.debug(ret)
if cls._response_ok(ret):
logger.info("Added SeAT user with username %s" % sanatized)
return sanatized, password
logger.info("Failed to add SeAT user with username %s" % sanatized)
logger.info("Added SeAT user with username %s" % sanitized)
return sanitized, password
logger.info("Failed to add SeAT user with username %s" % sanitized)
return None
@classmethod
@ -93,7 +97,7 @@ class SeatManager:
@classmethod
def enable_user(cls, username):
""" Enable user """
ret = SeatManager.exec_request('user/{}'.format(username), 'put', active=1)
ret = cls.exec_request('user/{}'.format(username), 'put', active=1)
logger.debug(ret)
if cls._response_ok(ret):
logger.info("Enabled SeAT user with username %s" % username)
@ -104,13 +108,12 @@ class SeatManager:
@classmethod
def update_user(cls, username, email, password):
""" Edit user info """
logger.debug("Updating SeAT username %s with email %s and password hash starting with %s" % (username, email,
password[0:5]))
ret = SeatManager.exec_request('user/{}'.format(username), 'put', email=email)
logger.debug("Updating SeAT username %s with email %s and password" % (username, email))
ret = cls.exec_request('user/{}'.format(username), 'put', email=email)
logger.debug(ret)
if not cls._response_ok(ret):
logger.warn("Failed to update email for username {}".format(username))
ret = SeatManager.exec_request('user/{}'.format(username), 'put', password=password)
ret = cls.exec_request('user/{}'.format(username), 'put', password=password)
logger.debug(ret)
if not cls._response_ok(ret):
logger.warn("Failed to update password for username {}".format(username))
@ -118,25 +121,25 @@ class SeatManager:
logger.info("Updated SeAT user with username %s" % username)
return username
@staticmethod
def update_user_password(username, email, plain_password=None):
@classmethod
def update_user_password(cls, username, email, plain_password=None):
logger.debug("Settings new SeAT password for user %s" % username)
if not plain_password:
plain_password = SeatManager.__generate_random_pass()
if SeatManager.update_user(username, email, plain_password):
plain_password = cls.__generate_random_pass()
if cls.update_user(username, email, plain_password):
return plain_password
@staticmethod
def check_user_status(username):
sanatized = str(SeatManager.__santatize_username(username))
logger.debug("Checking SeAT status for user %s" % sanatized)
ret = SeatManager.exec_request('user/{}'.format(sanatized), 'get')
@classmethod
def check_user_status(cls, username):
sanitized = str(cls.__sanitize_username(username))
logger.debug("Checking SeAT status for user %s" % sanitized)
ret = cls.exec_request('user/{}'.format(sanitized), 'get')
logger.debug(ret)
return ret
@staticmethod
def get_all_seat_eveapis():
seat_all_keys = SeatManager.exec_request('key', 'get')
@classmethod
def get_all_seat_eveapis(cls):
seat_all_keys = cls.exec_request('key', 'get')
seat_keys = {}
for key in seat_all_keys:
try:
@ -145,117 +148,132 @@ class SeatManager:
seat_keys[key["key_id"]] = None
return seat_keys
@classmethod
def synchronize_eveapis(cls, user=None):
# Fetch all of the API keys stored in SeAT already
seat_all_keys = cls.get_all_seat_eveapis()
@staticmethod
def synchronize_eveapis(user=None):
seat_all_keys = SeatManager.get_all_seat_eveapis()
userinfo = None
# retrieve only user-specific api keys if user is specified
if user:
keypars = EveManager.get_api_key_pairs(user)
try:
userinfo = SeatManager.check_user_status(user.seat.username)
except ObjectDoesNotExist:
pass
keypairs = EveManager.get_api_key_pairs(user)
else:
# retrieve all api keys instead
keypars = EveManager.get_all_api_key_pairs()
if keypars:
for keypar in keypars:
if keypar.api_id not in seat_all_keys.keys():
keypairs = EveManager.get_all_api_key_pairs()
for keypair in keypairs:
# Transfer the key if it isn't already in SeAT
if keypair.api_id not in seat_all_keys.keys():
# Add new keys
logger.debug("Adding Api Key with ID %s" % keypar.api_id)
ret = SeatManager.exec_request('key', 'post', key_id=keypar.api_id, v_code=keypar.api_key)
logger.debug("Adding Api Key with ID %s" % keypair.api_id)
try:
ret = cls.exec_request('key', 'post',
key_id=keypair.api_id,
v_code=keypair.api_key,
raise_for_status=True)
logger.debug(ret)
except requests.HTTPError as e:
if e.response.status_code == 400:
logger.debug("API key already exists")
else:
logger.exception("API key sync failed")
continue # Skip the rest of the key processing
else:
# remove it from the list so it doesn't get deleted in the last step
seat_all_keys.pop(keypar.api_id)
if not userinfo: # TODO: should the following be done only for new keys?
# Check the key's user status
logger.debug("Retrieving user name from Auth's SeAT users database")
seat_all_keys.pop(keypair.api_id)
# Attach API key to the users SeAT account, if possible
try:
if keypar.user.seat.username:
logger.debug("Retrieving user %s info from SeAT users database" % keypar.user.seat.username)
userinfo = SeatManager.check_user_status(keypar.user.seat.username)
except ObjectDoesNotExist:
pass
if userinfo:
try:
# If the user has activated seat, assign the key to him.
userinfo = cache.get_or_set('seat_user_status_' + cls.username_hash(keypair.user.seat.username),
lambda: cls.check_user_status(keypair.user.seat.username),
300) # Cache for 5 minutes
if not bool(userinfo):
# No SeAT account, skip
logger.debug("Could not find users SeAT id, cannot assign key to them")
continue
# If the user has activated seat, assign the key to them
logger.debug("Transferring Api Key with ID %s to user %s with ID %s " % (
keypar.api_id,
keypar.user.seat.username,
keypair.api_id,
keypair.user.seat.username,
userinfo['id']))
ret = SeatManager.exec_request('key/transfer/{}/{}'.format(keypar.api_id, userinfo['id']),
ret = cls.exec_request('key/transfer/{}/{}'.format(keypair.api_id, userinfo['id']),
'get')
logger.debug(ret)
except ObjectDoesNotExist:
logger.debug("User does not have SeAT activated, could not assign key to user")
if bool(seat_all_keys) and not user and hasattr(settings, 'SEAT_PURGE_DELETED') and settings.SEAT_PURGE_DELETED:
if bool(seat_all_keys) and not user and getattr(settings, 'SEAT_PURGE_DELETED', False):
# remove from SeAT keys that were removed from Auth
for key, key_user in iteritems(seat_all_keys):
# Remove the key only if it is an account or character key
ret = SeatManager.exec_request('key/{}'.format(key), 'get')
ret = cls.exec_request('key/{}'.format(key), 'get')
logger.debug(ret)
try:
if (ret['info']['type'] == "Account") or (ret['info']['type'] == "Character"):
logger.debug("Removing api key %s from SeAT database" % key)
ret = SeatManager.exec_request('key/{}'.format(key), 'delete')
ret = cls.exec_request('key/{}'.format(key), 'delete')
logger.debug(ret)
except KeyError:
pass
@staticmethod
def get_all_roles():
@classmethod
def get_all_roles(cls):
groups = {}
ret = SeatManager.exec_request('role', 'get')
ret = cls.exec_request('role', 'get')
logger.debug(ret)
for group in ret:
groups[group["title"]] = group["id"]
logger.debug("Retrieved role list from SeAT: %s" % str(groups))
return groups
@staticmethod
def add_role(role):
ret = SeatManager.exec_request('role/new', 'post', name=role)
@classmethod
def add_role(cls, role):
ret = cls.exec_request('role/new', 'post', name=role)
logger.debug(ret)
logger.info("Added Seat group %s" % role)
role_info = SeatManager.exec_request('role/detail/{}'.format(role), 'get')
role_info = cls.exec_request('role/detail/{}'.format(role), 'get')
logger.debug(role_info)
return role_info["id"]
@staticmethod
def add_role_to_user(user_id, role_id):
ret = SeatManager.exec_request('role/grant-user-role/{}/{}'.format(user_id, role_id), 'get')
@classmethod
def add_role_to_user(cls, user_id, role_id):
ret = cls.exec_request('role/grant-user-role/{}/{}'.format(user_id, role_id), 'get')
logger.info("Added role %s to user %s" % (role_id, user_id))
return ret
@staticmethod
def revoke_role_from_user(user_id, role_id):
ret = SeatManager.exec_request('role/revoke-user-role/{}/{}'.format(user_id, role_id), 'get')
@classmethod
def revoke_role_from_user(cls, user_id, role_id):
ret = cls.exec_request('role/revoke-user-role/{}/{}'.format(user_id, role_id), 'get')
logger.info("Revoked role %s from user %s" % (role_id, user_id))
return ret
@staticmethod
def update_roles(seat_user, roles):
@classmethod
def update_roles(cls, seat_user, roles):
logger.debug("Updating SeAT user %s with roles %s" % (seat_user, roles))
user_info = SeatManager.check_user_status(seat_user)
user_info = cls.check_user_status(seat_user)
user_roles = {}
if type(user_info["roles"]) is list:
for role in user_info["roles"]:
user_roles[role["title"]] = role["id"]
logger.debug("Got user %s SeAT roles %s" % (seat_user, user_roles))
seat_roles = SeatManager.get_all_roles()
seat_roles = cls.get_all_roles()
addroles = set(roles) - set(user_roles.keys())
remroles = set(user_roles.keys()) - set(roles)
logger.info("Updating SeAT roles for user %s - adding %s, removing %s" % (seat_user, addroles, remroles))
for r in addroles:
if r not in seat_roles:
seat_roles[r] = SeatManager.add_role(r)
seat_roles[r] = cls.add_role(r)
logger.debug("Adding role %s to SeAT user %s" % (r, seat_user))
SeatManager.add_role_to_user(user_info["id"], seat_roles[r])
cls.add_role_to_user(user_info["id"], seat_roles[r])
for r in remroles:
logger.debug("Removing role %s from user %s" % (r, seat_user))
SeatManager.revoke_role_from_user(user_info["id"], seat_roles[r])
cls.revoke_role_from_user(user_info["id"], seat_roles[r])
@staticmethod
def username_hash(username):
m = hashlib.sha1()
m.update(username)
return m.hexdigest()

View File

@ -1,6 +1,8 @@
from __future__ import unicode_literals
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib import messages
from django.utils.translation import ugettext_lazy as _
from .manager import SeatManager
@ -15,6 +17,7 @@ import logging
logger = logging.getLogger(__name__)
ACCESS_PERM = 'seat.access_seat'
SERVICE_NAME = {'service': 'SeAT'}
@login_required
@ -39,6 +42,8 @@ def activate_seat(request):
logger.debug("Updated SeatUser for user %s with SeAT credentials. Adding eve-apis..." % request.user)
SeatTasks.update_roles.delay(request.user.pk)
logger.info("Successfully activated SeAT for user %s" % request.user)
messages.add_message(request, messages.SUCCESS, _('Successfully activated your %(service)s account.') %
SERVICE_NAME)
SeatManager.synchronize_eveapis(request.user)
credentials = {
'username': request.user.seat.username,
@ -46,6 +51,9 @@ def activate_seat(request):
}
return render(request, 'registered/service_credentials.html',
context={'credentials': credentials, 'service': 'SeAT'})
messages.add_message(request, messages.ERROR,
_('Failed to activate your %(service)s account, please contact your administrator.') %
SERVICE_NAME)
logger.error("Unsuccessful attempt to activate seat for user %s" % request.user)
return redirect("auth_services")
@ -56,10 +64,15 @@ def deactivate_seat(request):
logger.debug("deactivate_seat called by user %s" % request.user)
# false we failed
if SeatTasks.delete_user(request.user):
messages.add_message(request, messages.SUCCESS,
_('Successfully deactivated your %(service)s account.') % SERVICE_NAME)
logger.info("Successfully deactivated SeAT for user %s" % request.user)
return redirect("auth_services")
else:
logging.error("User does not have a SeAT account")
messages.add_message(request, messages.ERROR,
_('Failed to deactivate your %(service)s account, please contact your administrator.') %
SERVICE_NAME)
logger.error("Unsuccessful attempt to activate SeAT for user %s" % request.user)
return redirect("auth_services")
@ -76,10 +89,15 @@ def reset_seat_password(request):
'username': request.user.seat.username,
'password': result,
}
messages.add_message(request, messages.SUCCESS,
_('Successfully reset your %(service)s password.') % {'service': 'SeAT'})
logger.info("Succesfully reset SeAT password for user %s" % request.user)
return render(request, 'registered/service_credentials.html',
context={'credentials': credentials, 'service': 'SeAT'})
logger.error("Unsuccessful attempt to reset SeAT password for user %s" % request.user)
messages.add_message(request, messages.ERROR,
_('Failed to reset your %(service)s password, please contact your administrator.') %
{'service': 'SeAT'})
return redirect("auth_services")
@ -98,11 +116,17 @@ def set_seat_password(request):
request.user.email,
plain_password=password)
if result:
messages.add_message(request, messages.SUCCESS,
_('Successfully set your %(service)s password.') % SERVICE_NAME)
logger.info("Succesfully reset SeAT password for user %s" % request.user)
return redirect("auth_services")
else:
messages.add_message(request, messages.ERROR,
_('Failed to set your %(service)s password, please contact your administrator.') %
SERVICE_NAME)
logger.error("Failed to install custom SeAT password for user %s" % request.user)
else:
messages.add_message(request, messages.ERROR, _('Invalid password.'))
logger.error("Invalid SeAT password provided")
else:
logger.debug("Request is not type POST - providing empty form.")