allianceauth/services/managers/eve_api_manager.py
Adarnof 1b4f5e4e88 Adarnof's Little Things (#547)
* Port to Django 1.10
Initial migrations for current states of all models. Requires faking to retain data.
Removed all references to render_to_response, replacing with render shortcut.
Same for HttpResponseRedirect to render shortcut.
Corrected notification signal import to wait for app registry to finish loading.

* Correct typos from render conversion

* Modify models to suppress Django field warnings

* Script for automatic database conversion
 - fakes initial migrations to preserve data
Include LOGIN_URL setting

* Correct context processor import typo

* Removed pathfinder support.
Current pathfinder versions require SSO, not APIs added to database.
Conditionally load additional database definitions only if services are enabled.
Prevents errors when running auth without creating all possible databases.

* Condense context processors

* Include Django 1.10 installation in migrate script
Remove syncdb/evolve, replace with migrate for update script

* Replaced member/blue perms with user state system
Removed sigtracker
Initial migrations for default perms and groups
Removed perm bootstrapping on first run

* Clean up services list

* Remove fleet fittings page

* Provide action feedback via django messaging
Display unread notification count
Correct left navbar alignment

* Stop storing service passwords.
Provide them one time upon activation or reset.
Closes #177

* Add group sync buttons to admin site
Allow searcing of AuthServicesInfo models
Display user main character

* Correct button CSS to remove underlines on hover

* Added bulk actions to notifications
Altered notification default ordering

* Centralize API key validation.
Remove unused error count on API key model.
Restructure API key refresh task to queue all keys per user and await completion.
Closes #350

* Example configuration files for supervisor.
Copy to /etc/supervisor/conf.d and restart to take effect.
Closes #521
Closes #266

* Pre-save receiver for member/blue state switching
Removed is_blue field
Added link to admin site

* Remove all hardcoded URLs from views and templates
Correct missing render arguments
Closes #540

* Correct celeryd process directory

* Migration to automatically set user states.
Runs instead of waiting for next API refresh cycle. Should make the transition much easier.

* Verify service accounts accessible to member state

* Restructure project to remove unnecessary apps.
(celerytask, util, portal, registraion apps)
Added workarounds for python 3 compatibility.

* Correct python2 compatibility

* Check services against state being changed to

* Python3 compatibility fixes

* Relocate x2bool py3 fix

* SSO integration for logging in to existing accounts.

* Add missing url names for fleetup reverse

* Sanitize groupnames before syncing.

* Correct trailing slash preventing url resolution

* Alter group name sanitization to allow periods and hyphens

* Correct state check on pre_save model for corp/alliance group assignment

* Remove sigtracker table from old dbs to allow user deletion

* Include missing celery configuration

* Teamspeak error handling

* Prevent celery worker deadlock on async group result wait

* Correct active navbar links for translated urls.
Correct corp status url resolution for some links.
Remove DiscordAuthToken model.
2016-10-16 18:01:14 -04:00

342 lines
15 KiB
Python

from __future__ import unicode_literals
import evelink.api
import evelink.char
import evelink.eve
from authentication.states import MEMBER_STATE, BLUE_STATE
from authentication.models import AuthServicesInfo
from eveonline.models import EveCharacter
from django.conf import settings
import logging
logger = logging.getLogger(__name__)
class EveApiManager:
def __init__(self):
pass
class ApiValidationError(Exception):
def __init__(self, msg, api_id):
self.msg = msg
self.api_id = api_id
def __str__(self):
return self.msg
class ApiMaskValidationError(ApiValidationError):
def __init__(self, required_mask, api_mask, api_id):
msg = 'Insufficient API mask provided. Required: %s Got: %s' % (required_mask, api_mask)
self.required_mask = required_mask
self.api_mask = api_mask
super(EveApiManager.ApiMaskValidationError, self).__init__(msg, api_id)
class ApiAccountValidationError(ApiValidationError):
def __init__(self, api_id):
msg = 'Insufficient API access provided. Full account access is required, got character restricted.'
super(EveApiManager.ApiAccountValidationError, self).__init__(msg, api_id)
class ApiInvalidError(ApiValidationError):
def __init__(self, api_id):
msg = 'Key is invalid.'
super(EveApiManager.ApiInvalidError, self).__init__(msg, api_id)
@staticmethod
def get_characters_from_api(api_id, api_key):
logger.debug("Getting characters from api id %s" % api_id)
api = evelink.api.API(api_key=(api_id, api_key))
# Should get characters
account = evelink.account.Account(api=api)
chars = account.characters()
logger.debug("Retrieved characters %s from api id %s" % (chars, api_id))
return chars
@staticmethod
def get_corporation_ticker_from_id(corp_id):
logger.debug("Getting ticker for corp id %s" % corp_id)
api = evelink.api.API()
corp = evelink.corp.Corp(api)
response = corp.corporation_sheet(corp_id)
logger.debug("Retrieved corp sheet for id %s: %s" % (corp_id, response))
ticker = response[0]['ticker']
logger.debug("Determined corp id %s ticker: %s" % (corp_id, ticker))
return ticker
@staticmethod
def get_alliance_information(alliance_id):
logger.debug("Getting info for alliance with id %s" % alliance_id)
api = evelink.api.API()
eve = evelink.eve.EVE(api=api)
alliance = eve.alliances()
results = alliance[0][int(alliance_id)]
logger.debug("Got alliance info %s" % results)
return results
@staticmethod
def get_corporation_information(corp_id):
logger.debug("Getting info for corp with id %s" % corp_id)
api = evelink.api.API()
corp = evelink.corp.Corp(api=api)
corpinfo = corp.corporation_sheet(corp_id=int(corp_id))
results = corpinfo[0]
logger.debug("Got corp info %s" % results)
return results
@staticmethod
def get_api_info(api_id, api_key):
api = evelink.api.API(api_key=(api_id, api_key))
account = evelink.account.Account(api=api)
return account.key_info()[0]
@staticmethod
def check_api_is_type_account(api_id, api_key):
logger.debug("Checking if api id %s is account." % api_id)
api = evelink.api.API(api_key=(api_id, api_key))
account = evelink.account.Account(api=api)
info = account.key_info()
logger.debug("API id %s is type %s" % (api_id, info[0]['type']))
return info[0]['type'] == "account"
@staticmethod
def check_api_is_full(api_id, api_key):
logger.debug("Checking if api id %s meets member requirements." % api_id)
api = evelink.api.API(api_key=(api_id, api_key))
account = evelink.account.Account(api=api)
info = account.key_info()
logger.debug("API has mask %s, required is %s" % (info[0]['access_mask'], settings.MEMBER_API_MASK))
return info[0]['access_mask'] & int(settings.MEMBER_API_MASK) == int(settings.MEMBER_API_MASK)
@staticmethod
def check_blue_api_is_full(api_id, api_key):
logger.debug("Checking if api id %s meets blue requirements." % api_id)
api = evelink.api.API(api_key=(api_id, api_key))
account = evelink.account.Account(api=api)
info = account.key_info()
logger.debug("API has mask %s, required is %s" % (info[0]['access_mask'], settings.BLUE_API_MASK))
return info[0]['access_mask'] & int(settings.BLUE_API_MASK) == int(settings.BLUE_API_MASK)
@staticmethod
def get_api_info(api_id, api_key):
logger.debug("Getting api info for key id %s" % api_id)
api = evelink.api.API(api_key=(api_id, api_key))
account = evelink.account.Account(api=api)
info = account.key_info()
logger.debug("Got info for api id %s: %s" % (api_id, info))
return info
@staticmethod
def api_key_is_valid(api_id, api_key):
logger.debug("Checking if api id %s is valid." % api_id)
api = evelink.api.API(api_key=(api_id, api_key))
account = evelink.account.Account(api=api)
account.key_info()
logger.info("Verified api id %s is still valid." % api_id)
return True
@staticmethod
def check_if_api_server_online():
logger.debug("Checking if API server online.")
try:
api = evelink.api.API()
server = evelink.server.Server(api=api)
server.server_status()
logger.info("Verified API server is online and reachable.")
return True
except evelink.api.APIError:
logger.exception("APIError occured while trying to query api server. Possibly offline?")
logger.warn("Unable to reach API server.")
return False
@staticmethod
def check_if_id_is_corp(corp_id):
logger.debug("Checking if id %s is a corp." % corp_id)
try:
api = evelink.api.API()
corp = evelink.corp.Corp(api=api)
corpinfo = corp.corporation_sheet(corp_id=int(corp_id))
assert corpinfo[0]
logger.debug("Confirmed id %s is a corp." % corp_id)
return True
except evelink.api.APIError as error:
if int(error.code) == '523':
logger.debug("Confirmed id %s is not a corp" % corp_id)
return False
logger.debug("APIError occured while checking if id %s is corp. Possibly not corp?" % corp_id)
logger.debug("Unable to verify id %s is corp." % corp_id)
return False
@staticmethod
def get_corp_standings():
if settings.CORP_API_ID and settings.CORP_API_VCODE:
logger.debug("Getting corp standings with api id %s" % settings.CORP_API_ID)
api = evelink.api.API(api_key=(settings.CORP_API_ID, settings.CORP_API_VCODE))
corp = evelink.corp.Corp(api=api)
corpinfo = corp.contacts()
results = corpinfo.result
logger.debug("Got corp standings from settings: %s" % results)
return results
else:
logger.warn("No corp API key supplied in settings. Unable to get standings.")
return {}
@staticmethod
def get_corp_membertracking(api, vcode):
try:
logger.debug("Getting corp membertracking with api id %s" % settings.CORP_API_ID)
api = evelink.api.API(api_key=(api, vcode))
corp = evelink.corp.Corp(api=api)
membertracking = corp.members()
results = membertracking.result
logger.debug("Got corp membertracking from settings: %s" % results)
return results
except evelink.api.APIError:
logger.exception("Unhandled APIError occured.")
return {}
@staticmethod
def check_if_id_is_alliance(alliance_id):
logger.debug("Checking if id %s is an alliance." % alliance_id)
try:
api = evelink.api.API()
eve = evelink.eve.EVE(api=api)
alliance = eve.alliances()
results = alliance.result[int(alliance_id)]
if results:
logger.debug("Confirmed id %s is an alliance." % alliance_id)
return True
except evelink.api.APIError:
logger.exception(
"APIError occured while checking if id %s is an alliance. Possibly not alliance?" % alliance_id)
except KeyError:
logger.debug("Alliance with id %s not found in active alliance list." % alliance_id)
return False
logger.debug("Unable to verify id %s is an an alliance." % alliance_id)
return False
@staticmethod
def check_if_id_is_character(character_id):
logger.debug("Checking if id %s is a character." % character_id)
try:
api = evelink.api.API()
eve = evelink.eve.EVE(api=api)
results = eve.character_info_from_id(character_id)
if results:
logger.debug("Confirmed id %s is a character." % character_id)
return True
except evelink.api.APIError:
logger.debug(
"APIError occured while checking if id %s is a character. Possibly not character?" % character_id,
exc_info=True)
logger.debug("Unable to verify id %s is a character." % character_id)
return False
@staticmethod
def check_if_alliance_exists(alliance_id):
logger.debug("Checking if alliance id %s exists." % alliance_id)
try:
api = evelink.api.API()
eve = evelink.eve.EVE(api=api)
alliances = eve.alliances()
if int(alliance_id) in alliances[0]:
logger.debug("Verified alliance id %s exists." % alliance_id)
return True
else:
logger.debug("Verified alliance id %s does not exist." % alliance_id)
return False
except evelink.api.APIError:
logger.exception("Unhandled APIError occured.")
return False
except ValueError:
# attempts to catch error resulting from checking alliance_of nonetype models
logger.exception("Unhandled ValueError occured. Possible nonetype alliance model.")
return False
except:
logger.warn("Exception prevented verification of alliance id %s existance. Assuming false." % alliance_id)
return False
@staticmethod
def check_if_corp_exists(corp_id):
logger.debug("Checking if corp id %s exists." % corp_id)
try:
api = evelink.api.API()
corp = evelink.corp.Corp(api=api)
corpinfo = corp.corporation_sheet(corp_id=corp_id)
if corpinfo[0]['members']['current'] > 0:
logger.debug(
"Verified corp id %s exists with member count %s" % (corp_id, corpinfo[0]['members']['current']))
return True
else:
logger.debug(
"Verified corp id %s has closed. Member count %s" % (corp_id, corpinfo[0]['members']['current']))
return False
except evelink.api.APIError:
# could be smart and check for error code523 to verify error due to no corp instead of catch-all
logger.exception("Unhandled APIError occured.")
return False
except:
logger.warn("Exception prevented verification of corp id %s existance. Assuming false." % corp_id)
return False
@staticmethod
def validate_member_api(api_id, api_key):
if settings.MEMBER_API_ACCOUNT:
if EveApiManager.check_api_is_type_account(api_id, api_key) is False:
logger.info("Api id %s is not type account as required for members - failed validation." % api_id)
return False
if EveApiManager.check_api_is_full(api_id, api_key) is False:
logger.info("Api id %s does not meet member access mask requirements - failed validation." % api_id)
return False
return True
@staticmethod
def validate_blue_api(api_id, api_key):
if settings.BLUE_API_ACCOUNT:
if EveApiManager.check_api_is_type_account(api_id, api_key) is False:
logger.info("Api id %s is not type account as required for blues - failed validation." % api_id)
return False
if EveApiManager.check_blue_api_is_full(api_id, api_key) is False:
logger.info("Api id %s does not meet minimum blue access mask requirements - failed validation." % api_id)
return False
return True
@staticmethod
def validate_api(api_id, api_key, user):
try:
info = EveApiManager.get_api_info(api_id, api_key).result
chars = EveApiManager.get_characters_from_api(api_id, api_key).result
except evelink.api.APIError as e:
if int(e.code) in [221, 222]:
raise e
raise EveApiManager.ApiInvalidError(api_id)
except Exception:
raise EveApiManager.ApiInvalidError(api_id)
auth, c = AuthServicesInfo.objects.get_or_create(user=user)
states = [auth.state]
from authentication.tasks import determine_membership_by_character # circular import issue
for char in chars:
evechar = EveCharacter()
evechar.character_name = chars[char]['name']
evechar.corporation_id = chars[char]['corp']['id']
evechar.alliance_id = chars[char]['alliance']['id']
states.append(determine_membership_by_character(evechar))
if MEMBER_STATE not in states and BLUE_STATE not in states:
# default to requiring member keys for applications
states.append(MEMBER_STATE)
logger.debug('Checking API %s for states %s' % (api_id, states))
for state in states:
if (state == MEMBER_STATE and settings.MEMBER_API_ACCOUNT) or (
state == BLUE_STATE and settings.BLUE_API_ACCOUNT):
if info['type'] != 'account':
raise EveApiManager.ApiAccountValidationError(api_id)
if state == MEMBER_STATE:
if int(info['access_mask']) & int(settings.MEMBER_API_MASK) != int(settings.MEMBER_API_MASK):
raise EveApiManager.ApiMaskValidationError(settings.MEMBER_API_MASK, info['access_mask'], api_id)
elif state == BLUE_STATE:
if int(info['access_mask']) & int(settings.BLUE_API_MASK) != int(settings.BLUE_API_MASK):
raise EveApiManager.ApiMaskValidationError(settings.BLUE_API_MASK, info['access_mask'], api_id)
return True