Browse Source

Add logging throughout API and auth code.

master
Ben Kurtovic 7 years ago
parent
commit
5f289aec9e
5 changed files with 82 additions and 18 deletions
  1. +5
    -0
      calefaction/__init__.py
  2. +37
    -2
      calefaction/auth.py
  3. +4
    -3
      calefaction/eve/__init__.py
  4. +14
    -5
      calefaction/eve/esi.py
  5. +22
    -8
      calefaction/eve/sso.py

+ 5
- 0
calefaction/__init__.py View File

@@ -1,2 +1,7 @@
import logging

__version__ = "0.1.dev0" __version__ = "0.1.dev0"
__release__ = "0.1" __release__ = "0.1"

baseLogger = logging.getLogger("calefaction")
del logging

+ 37
- 2
calefaction/auth.py View File

@@ -5,6 +5,7 @@ from datetime import datetime, timedelta
from flask import g, session, url_for from flask import g, session, url_for
from itsdangerous import BadSignature, URLSafeSerializer from itsdangerous import BadSignature, URLSafeSerializer


from . import baseLogger
from .exceptions import AccessDeniedError from .exceptions import AccessDeniedError


__all__ = ["AuthManager"] __all__ = ["AuthManager"]
@@ -19,10 +20,14 @@ class AuthManager:
self._config = config self._config = config
self._eve = eve self._eve = eve


self._logger = baseLogger.getChild("auth")
self._debug = self._logger.debug

def _get_session_id(self): def _get_session_id(self):
"""Return the current session ID, allocating a new one if necessary.""" """Return the current session ID, allocating a new one if necessary."""
if "id" not in session: if "id" not in session:
session["id"] = g.db.new_session() session["id"] = g.db.new_session()
self._debug("Allocated session id=%d", session["id"])
g._session_checked = True g._session_checked = True
g._session_expired = False g._session_expired = False
return session["id"] return session["id"]
@@ -33,7 +38,9 @@ class AuthManager:
Remove it from the database and from the user's cookies. Remove it from the database and from the user's cookies.
""" """
if "id" in session: if "id" in session:
g.db.drop_session(session["id"])
sid = session["id"]
g.db.drop_session(sid)
self._debug("Dropped session id=%d", sid)
del session["id"] del session["id"]


def _check_session(self): def _check_session(self):
@@ -51,6 +58,7 @@ class AuthManager:
g._session_checked = check = g.db.has_session(session["id"]) g._session_checked = check = g.db.has_session(session["id"])
if not check: if not check:
g._session_expired = True g._session_expired = True
self._debug("Session expired id=%d", session["id"])
self._invalidate_session() self._invalidate_session()
return check return check


@@ -74,9 +82,13 @@ class AuthManager:
try: try:
value = serializer.loads(state) value = serializer.loads(state)
except BadSignature: except BadSignature:
self._debug("Bad signature for session id=%d", session["id"])
self._invalidate_session() self._invalidate_session()
return False return False

if value != session["id"]: if value != session["id"]:
self._debug("Got session id=%d, expected id=%d", value,
session["id"])
self._invalidate_session() self._invalidate_session()
return False return False
return True return True
@@ -117,23 +129,29 @@ class AuthManager:
""" """
result = g.db.get_auth(cid) result = g.db.get_auth(cid)
if not result: if not result:
self._debug("No auth info in database for char id=%d", cid)
return None return None


token, expires, refresh = result token, expires, refresh = result
seconds_til_expiry = (expires - datetime.utcnow()).total_seconds() seconds_til_expiry = (expires - datetime.utcnow()).total_seconds()
if seconds_til_expiry >= self.EXPIRY_THRESHOLD: if seconds_til_expiry >= self.EXPIRY_THRESHOLD:
self._debug("Using cached access token for char id=%d", cid)
return token return token


result = self._fetch_new_token(refresh, refresh=True) result = self._fetch_new_token(refresh, refresh=True)
if not result: if not result:
self._debug("Couldn't refresh token for char id=%d", cid)
g.db.drop_auth(cid) g.db.drop_auth(cid)
return None return None


token, expires, refresh, char_id, char_name = result token, expires, refresh, char_id, char_name = result
if char_id != cid: if char_id != cid:
self._debug("Refreshed token has incorrect char id=%d for "
"char id=%d", char_id, cid)
g.db.drop_auth(cid) g.db.drop_auth(cid)
return None return None


self._debug("Using fresh access token for char id=%d", cid)
g.db.put_character(cid, char_name) g.db.put_character(cid, char_name)
g.db.update_auth(cid, token, expires, refresh) g.db.update_auth(cid, token, expires, refresh)
return token return token
@@ -145,6 +163,8 @@ class AuthManager:
""" """
resp = self._eve.esi(token).v3.characters(char_id).get() resp = self._eve.esi(token).v3.characters(char_id).get()
if resp.get("corporation_id") != self._config.get("corp.id"): if resp.get("corporation_id") != self._config.get("corp.id"):
self._debug("Access denied per corp membership for char id=%d "
"session id=%d", char_id, session["id"])
g.db.drop_auth(char_id) g.db.drop_auth(char_id)
self._invalidate_session() self._invalidate_session()
raise AccessDeniedError() raise AccessDeniedError()
@@ -184,17 +204,24 @@ class AuthManager:


EVEAPIError or AccessDeniedError may be raised. EVEAPIError or AccessDeniedError may be raised.
""" """
if "id" in session:
self._debug("Checking auth for session id=%d", session["id"])

cid = self.get_character_id() cid = self.get_character_id()
if not cid: if not cid:
return False return False


token = self._get_token(cid) token = self._get_token(cid)
if not token: if not token:
self._debug("No valid token for char id=%d session id=%d", cid,
session["id"])
self._invalidate_session() self._invalidate_session()
return False return False


self._check_access(token, cid) self._check_access(token, cid)


self._debug("Access granted for char id=%d session id=%d", cid,
session["id"])
g.db.touch_session(session["id"]) g.db.touch_session(session["id"])
return True return True


@@ -219,20 +246,25 @@ class AuthManager:
""" """
if not code or not state: if not code or not state:
return False return False

if "id" in session:
self._debug("Logging in session id=%d", session["id"])
if not self._check_session(): if not self._check_session():
return False return False
if not self._verify_state_hash(state): if not self._verify_state_hash(state):
return False return False


sid = session["id"]
result = self._fetch_new_token(code) result = self._fetch_new_token(code)
if not result: if not result:
self._debug("Couldn't fetch token for session id=%d", sid)
self._invalidate_session() self._invalidate_session()
return False return False


token, expires, refresh, char_id, char_name = result token, expires, refresh, char_id, char_name = result
self._check_access(token, char_id) self._check_access(token, char_id)


sid = session["id"]
self._debug("Logged in char id=%d session id=%d", char_id, sid)
g.db.put_character(char_id, char_name) g.db.put_character(char_id, char_name)
g.db.set_auth(char_id, token, expires, refresh) g.db.set_auth(char_id, token, expires, refresh)
g.db.attach_session(sid, char_id) g.db.attach_session(sid, char_id)
@@ -244,5 +276,8 @@ class AuthManager:


Invalidates their session and clears the session cookie. Invalidates their session and clears the session cookie.
""" """
if "id" in session:
self._debug("Logging out session id=%d", session["id"])

self._invalidate_session() self._invalidate_session()
session.clear() session.clear()

+ 4
- 3
calefaction/eve/__init__.py View File

@@ -8,7 +8,7 @@ from .clock import Clock
from .esi import EVESwaggerInterface from .esi import EVESwaggerInterface
from .image import ImageServer from .image import ImageServer
from .sso import SSOManager from .sso import SSOManager
from .. import __release__
from .. import __release__, baseLogger


__all__ = ["EVE"] __all__ = ["EVE"]


@@ -19,11 +19,12 @@ class EVE:
session = requests.Session() session = requests.Session()
agent = self._get_user_agent(config.get("site.contact")) agent = self._get_user_agent(config.get("site.contact"))
session.headers["User-Agent"] = agent session.headers["User-Agent"] = agent
logger = baseLogger.getChild("eve")


self._clock = Clock() self._clock = Clock()
self._esi = EVESwaggerInterface(session)
self._esi = EVESwaggerInterface(session, logger.getChild("esi"))
self._image = ImageServer() self._image = ImageServer()
self._sso = SSOManager(session)
self._sso = SSOManager(session, logger.getChild("sso"))


@staticmethod @staticmethod
def _get_user_agent(contact): def _get_user_agent(contact):


+ 14
- 5
calefaction/eve/esi.py View File

@@ -130,8 +130,11 @@ class EVESwaggerInterface:
data = esi(token).v1.universe.names.post(ids=[entity_id]}) data = esi(token).v1.universe.names.post(ids=[entity_id]})
""" """


def __init__(self, session):
def __init__(self, session, logger):
self._session = session self._session = session
self._logger = logger
self._debug = logger.debug

self._base_url = "https://esi.tech.ccp.is" self._base_url = "https://esi.tech.ccp.is"
self._data_source = "tranquility" self._data_source = "tranquility"
self._cache = _ESICache() self._cache = _ESICache()
@@ -148,8 +151,13 @@ class EVESwaggerInterface:
pkey = self._cache.freeze_dict(params) pkey = self._cache.freeze_dict(params)
key = "|".join((method.__name__, self._data_source, query, pkey)) key = "|".join((method.__name__, self._data_source, query, pkey))
cached = self._cache.fetch(key) cached = self._cache.fetch(key)
if cached:
return cached
else:
cached = None

self._debug("[%s] [%s] %s", method.__name__.upper(),
"cache" if cached else "fresh", query)
if cached:
return cached


headers = { headers = {
"Accept": "application/json", "Accept": "application/json",
@@ -164,8 +172,9 @@ class EVESwaggerInterface:
headers=headers, timeout=10) headers=headers, timeout=10)
resp.raise_for_status() resp.raise_for_status()
result = resp.json() if resp.content else None result = resp.json() if resp.content else None
except (requests.RequestException, ValueError) as exc:
raise EVEAPIError(str(exc))
except (requests.RequestException, ValueError):
self._logger.exception("ESI request failed")
raise EVEAPIError()


if can_cache and result is not None: if can_cache and result is not None:
self._cache.insert(key, result, resp) self._cache.insert(key, result, resp)


+ 22
- 8
calefaction/eve/sso.py View File

@@ -11,9 +11,12 @@ __all__ = ["SSOManager"]
class SSOManager: class SSOManager:
"""EVE API module for Single Sign-On (SSO).""" """EVE API module for Single Sign-On (SSO)."""


def __init__(self, session):
def __init__(self, session, logger):
self._session = session self._session = session


self._logger = logger
self._debug = logger.debug

def get_authorize_url(self, client_id, redirect_uri, scopes=None, def get_authorize_url(self, client_id, redirect_uri, scopes=None,
state=None): state=None):
"""Return a URL that the end user can use to start a login.""" """Return a URL that the end user can use to start a login."""
@@ -46,26 +49,32 @@ class SSOManager:
params["grant_type"] = "refresh_token" params["grant_type"] = "refresh_token"
else: else:
params["grant_type"] = "authorization_code" params["grant_type"] = "authorization_code"
self._debug("[POST] /oauth/token")


try: try:
resp = self._session.post(url, data=params, timeout=10, resp = self._session.post(url, data=params, timeout=10,
auth=(client_id, client_secret)) auth=(client_id, client_secret))
json = resp.json() json = resp.json()
except (requests.RequestException, ValueError) as exc:
raise EVEAPIError(str(exc))
except (requests.RequestException, ValueError):
self._logger.exception("Access token fetch failed")
raise EVEAPIError()


if not resp.ok or "error" in json: if not resp.ok or "error" in json:
self._debug("Access token fetch error: %s", json["error"])
return None return None


if json.get("token_type") != "Bearer": if json.get("token_type") != "Bearer":
raise EVEAPIError("invalid token_type in response body")
self._logger.error("Invalid token_type in response body: %s",
json.get("token_type"))
raise EVEAPIError()


token = json.get("access_token") token = json.get("access_token")
expiry = json.get("expires_in") expiry = json.get("expires_in")
refresh = json.get("refresh_token") refresh = json.get("refresh_token")


if not token or not expiry or not refresh: if not token or not expiry or not refresh:
raise EVEAPIError("missing data in token response body")
self._logger.error("Missing data in token response body")
raise EVEAPIError()


return token, expiry, refresh return token, expiry, refresh


@@ -78,19 +87,24 @@ class SSOManager:
""" """
url = "https://login.eveonline.com/oauth/verify" url = "https://login.eveonline.com/oauth/verify"
headers = {"Authorization": "Bearer " + token} headers = {"Authorization": "Bearer " + token}
self._debug("[GET] /oauth/verify")

try: try:
resp = self._session.get(url, timeout=10, headers=headers) resp = self._session.get(url, timeout=10, headers=headers)
json = resp.json() json = resp.json()
except (requests.RequestException, ValueError) as exc:
raise EVEAPIError(str(exc))
except (requests.RequestException, ValueError):
self._logger.exception("Access token verify failed")
raise EVEAPIError()


if not resp.ok or "error" in json: if not resp.ok or "error" in json:
self._debug("Access token verify error: %s", json["error"])
return None return None


char_id = json.get("CharacterID") char_id = json.get("CharacterID")
char_name = json.get("CharacterName") char_name = json.get("CharacterName")


if not char_id or not char_name: if not char_id or not char_name:
raise EVEAPIError("missing character ID or name in response body")
self._logger.error("Missing character ID or name in response body")
raise EVEAPIError()


return char_id, char_name return char_id, char_name

Loading…
Cancel
Save