Exemple #1
0
def test_char(charid):

    import common.request_esi
    import common.logger as _logger

    # get character affiliations

    affilliations = _esihelpers.esi_affiliations(charid)
    allianceid = affilliations.get('allianceid')
    charname = affilliations.get('charname')
    corpid = affilliations.get('corpid')

    if not allianceid:
        # no alliance no blue
        result = {'code': 0}
        return 200, result

    # test the character's alliance
    code, result = test_alliance(allianceid)

    return code, result
Exemple #2
0
def ban_character(charid, altof=None):
    # take a charid and ban it (technially, set it to pending approval)
    # flavor text added in other logic

    logger = logging.getLogger('tri_api.endpoints.blacklist.add.character')

    dn = 'ou=People,dc=triumvirate,dc=rocks'
    filterstr = 'uid={}'.format(charid)
    attrlist = ['characterName']
    code, result = _ldaphelpers.ldap_search(__name__, dn, filterstr, attrlist)

    if code == False:
        msg = 'unable to fetch ldap information: {}'.format(error)
        logger.error(msg)
        return False

    # if a stub is created, i would have to search again

    affiliations = _esihelpers.esi_affiliations(charid)

    charname = affiliations['charname']

    cn, dn = _ldaphelpers.ldap_normalize_charname(charname)

    if result == None:
        # not currently in ldap, create a stub
        _ldaphelpers.ldap_create_stub(charid=charid,
                                      authgroups=['public', 'ban_pending'],
                                      altof=altof)
    else:
        _ldaphelpers.add_value(dn, 'authGroup', 'ban_pending')

    msg = 'banning {0}'.format(charname)
    logger.info(msg)

    return True
Exemple #3
0
def makesession(charid):

    import common.logger as _logger
    import common.request_esi
    import common.credentials.core as _core
    import common.esihelpers as _esihelpers
    import common.database as _database

    import base64
    import urllib.parse
    import phpserialize
    import MySQLdb as mysql
    import json
    import uuid
    import os
    import hmac
    import hashlib
    import time
    import redis

    from Crypto import Random
    from Crypto.Cipher import AES

    key = _core.key

    # construct the user's session. this is how you auth to core.
    # we're mimicing laravel structure exactly. it is finikiy.
    payload = dict()

    # actually useful data
    payload['charID'] = charid
    payload['csrf_token'] = uuid.uuid4().hex
    payload['_previous'] = dict()
    payload['_previous']['url'] = 'https://auth.triumvirate.rocks/eve/callback'
    payload['ip_address'] = ''
    payload['user_agent'] = ''

    # i have literally no idea what purpose this serves.

    payload['_flash'] = dict()
    payload['_flash']['new'] = ''
    payload['_flash']['old'] = ''

    # see: vendor/symfony/http-foundation/Session/Storage/MetadataBag.php
    # for why the _sf2_meta shit is here
    # c: create, u: updated, l: lifetime
    payload['_sf2_meta'] = dict()
    payload['_sf2_meta']['u'] = time.time()
    payload['_sf2_meta']['l'] = 0
    payload['_sf2_meta']['c'] = time.time()

    # first, get the user data.

    affiliations = _esihelpers.esi_affiliations(charid)

    charname = affiliations.get('charname')
    payload['charName'] = charname
    payload['corpID'] = affiliations.get('corpid')
    payload['corpName'] = affiliations.get('corpname')
    payload['allianceID'] = affiliations.get('allianceid')
    payload['allianceName'] = affiliations.get('alliancename')

    # the scope controls whether you are tri or a tri blue
    # this in turn controls various access levels

    # this is legacy code for laravel - python services determine access in different ways.

    if payload['allianceID'] == 933731581:
        # "tri alliance only" scope
        payload['scope'] = 2
    else:
        payload['scope'] = 1

    payload = phpserialize.dumps(payload)
    payload = base64.b64encode(payload)

    # AES requires inputs to be multiples of 16
    #
    # laravel seems exceedingly picky with the session id size and padding
    # so we're going to mimic it exactly

    sessionid = uuid.uuid4().hex + uuid.uuid4().hex
    sessionid = sessionid[:40]

    # feed the session data into the session table
    # mysql is just to make laravel happy

    try:
        sql_conn = mysql.connect(database=_database.DB_DATABASE,
                                 user=_database.DB_USERNAME,
                                 password=_database.DB_PASSWORD,
                                 host=_database.DB_HOST)
    except mysql.Error as err:
        _logger.log('[' + __name__ + '] mysql error: ' + str(err),
                    _logger.LogLevel.ERROR)
        return False
    cursor = sql_conn.cursor()
    now = time.time()

    # make sure this is the only session. i would adjust table schema but that'd probably
    # break laravel
    # purge null sessions too
    try:
        query = 'DELETE FROM sessions WHERE charID = %s'
        cursor.execute(
            query,
            (charid, ),
        )
    except mysql.Error as err:
        _logger.log('[' + __name__ + '] mysql error: ' + str(err),
                    _logger.LogLevel.ERROR)
        return False
    try:
        query = 'INSERT INTO sessions (id, charID, charName, payload, last_activity) VALUES(%s, %s, %s, %s, %s)'
        cursor.execute(
            query,
            (
                sessionid,
                charid,
                charname,
                payload,
                now,
            ),
        )
    except mysql.Error as err:
        _logger.log('[' + __name__ + '] mysql error: ' + str(err),
                    _logger.LogLevel.ERROR)
        return False
    finally:
        cursor.close()
        sql_conn.commit()
        sql_conn.close()

    # store the session id + payload into redis

    r = redis.StrictRedis(host='localhost', port=6379, db=0)
    try:
        r.client_list()
    except redis.exceptions.ConnectionError as err:
        _logger.log('[' + __name__ + '] Redis connection error: ' + str(err),
                    _logger.LogLevel.ERROR)
    except redis.exceptions.ConnectionRefusedError as err:
        _logger.log('[' + __name__ + '] Redis connection error: ' + str(err),
                    _logger.LogLevel.ERROR)
    except Exception as err:
        logger.error('[' + __name__ + '] Redis generic error: ' + str(err))

    try:
        # store the session in redis and set it to timeout in a week
        r.set(sessionid, payload)
        expires = 86400 * 7  # 1 week
        r.expire(sessionid, expires)
    except Exception as err:
        _logger.log('[' + __name__ + '] Redis error: ' + str(err),
                    _logger.LogLevel.ERROR)

    # construct the encrypted cookie contents, which consists of three things:
    # the initialization vector for AES, a sha256 hash, and the AES encrypted session id

    key = base64.b64decode(key)
    iv = uuid.uuid4(
    ).hex[:16]  # need a 16 digit initial value for the encryption
    iv = iv.encode('utf-8')

    # structure the php serialized thing in a way that laravel likes
    # don't rock the boat

    sessionid = 's:40:"' + str(
        sessionid
    ) + '";\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
    sessionid = sessionid.encode('utf-8')

    _logger.log(
        '[' + __name__ +
        '] key length: {0}, iv length: {1}, sessionid length: {2}'.format(
            len(key), len(iv), len(sessionid)), _logger.LogLevel.DEBUG)

    # the madness as demanded by laravel
    # see: vendor/laravel/framework/src/Illuminate/Encryption/Encrypter.php

    try:
        value = AES.new(key, AES.MODE_CBC, iv).encrypt(sessionid)
    except Exception as error:
        # how this can happen with fixed lengths i'm unclear...
        _logger.log('[' + __name__ + '] AES error: ' + str(error),
                    _logger.LogLevel.ERROR)
        return False

    value = base64.b64encode(value)
    iv = base64.b64encode(iv)

    hash = hmac.new(key, msg=iv + value, digestmod=hashlib.sha256)

    cookie = dict()
    cookie['mac'] = hash.hexdigest()
    cookie['iv'] = iv.decode('utf-8')
    cookie['value'] = value.decode('utf-8')

    cookie = json.dumps(cookie)
    cookie = base64.b64encode(cookie.encode('utf-8'))
    cookie = cookie.decode('utf-8')

    return cookie
Exemple #4
0
def fetch_chardetails(charid):

    chardetails = dict()

    logger = getlogger('core.corpaudit.chardetails.{0}'.format(charid))

    dn = 'ou=People,dc=triumvirate,dc=rocks'
    filterstr='(uid={})'.format(charid)
    attrlist=['characterName', 'authGroup', 'teamspeakdbid', 'esiAccessToken', 'altOf', 'corporation', 'lastKill', 'lastKillTime','corporationName']
    code, result = _ldaphelpers.ldap_search(__name__, dn, filterstr, attrlist)

    if result is None or not code:
        # no result? simple response.
        chardetails['location'] = 'Unknown'
        chardetails['corporation'] = 'Unknown'
        chardetails['online'] = 'Unknown'
        chardetails['last_online'] = 'Unknown'
        chardetails['token_status'] = False
        chardetails['teamspeak_status'] = False
        chardetails['isalt'] = 'Unknown'
        chardetails['lastKill'] = None
        chardetails['lastKillTime'] = None
        chardetails['altof'] = None

        # fetch affiliations

        affiliations = _esihelpers.esi_affiliations(charid)

        chardetails['corporation'] = affiliations.get('corpname')
        chardetails['charname'] = affiliations.get('charname')

    else:
        try:
            (dn, info), = result.items()
        except ValueError:
            print("error: {}".format(charid))

        chardetails['charname'] = info['characterName']

        # convert last kill time into human readable

        try:
            chardetails['lastKill'] = info['lastKill']
        except Exception as e:
            chardetails['lastKill'] = None

        try:
            killtime = int(info['lastKillTime'])
            killtime = time.strftime("%Y/%m/%d, %H:%M:%S", time.localtime(killtime))
            chardetails['lastKillTime'] = killtime
        except Exception as e:
            chardetails['lastKillTime'] = None

        corp_id = info['corporation']

        if corp_id is None:
            print("error: {} no corp".format(charid))

        chardetails['corporation'] = info['corporationName']

        # does the char have a token?

        try:
            detail = info['esiAccessToken']
            if len(detail) > 0:
                chardetails['token_status'] = True
            else:
                chardetails['token_status'] = False
        except Exception as e:
            chardetails['token_status'] = False

        # teamspeak registration?
        try:
            detail = info['teamspeakdbid']
            if len(detail) > 0:
                chardetails['teamspeak_status'] = True
            else:
                chardetails['teamspeak_status'] = False
        except Exception as e:
            chardetails['teamspeak_status'] = False

        # is this an alt?

        # cast the altof detail to something useful

        try:
            detail = info['altOf']
        except Exception as e:
            detail = None

        # str(None) == False
        if str(detail).isdigit():
            chardetails['isalt'] = True
            request_url = 'characters/{0}/'.format(detail)
            code, result = common.request_esi.esi(__name__, request_url, 'get')

            if not code == 200:
                msg = '/characters/{0}/ API error {1}: {2}'.format(detail, code, result)
                logger.warning(msg)
            try:
                chardetails['altof'] = result['name']
            except KeyError as error:
                msg = 'User does not exist: {0})'.format(charid)
                logger.error(msg)
                chardetails['altof'] = 'Unknown'
        else:
            chardetails['altof'] = None
            chardetails['isalt'] = False

        ## start fetching character-specific information

        #
        request_url = 'characters/{0}/location/'.format(charid)
        code, result = common.request_esi.esi(__name__, request_url, method='get', charid=charid, version='v1')

        if not code == 200:
            # it doesn't really matter
            msg = 'characters loction API error {0}: {1}'.format(code, result)
            logger.debug(msg)
            location = None
            chardetails['location_id'] = location
            chardetails['location'] = 'Unknown'
        else:
            # can include either station_id or structure_id
            location = result['solar_system_id']
            chardetails['location_id'] = location

        request_url = 'characters/{0}/location/'.format(charid)
        code, result = common.request_esi.esi(__name__, request_url, method='get', charid=charid, version='v1')

        if not code == 200:
            # it doesn't really matter
            msg = 'characters loction API error {0}: {1}'.format(code, result)
            logger.debug(msg)
            location = None
        else:
            # can include either station_id or structure_id
            location = result['solar_system_id']

        chardetails['location_id'] = location

        # map the location to a name
        if location == None:
            chardetails['location'] = 'Unknown'
        else:
            request_url = 'universe/systems/{0}/'.format(location)
            code, result = common.request_esi.esi(__name__, request_url, 'get', version='v4')
            if not code == 200:
                msg = '{0} API error: {1}'.format(request_url, result)
                logger.error(msg)
                chardetails['location'] = 'Unknown'
            else:
                chardetails['location'] = result['name']

        # get online status

        request_url = 'characters/{0}/online/'.format(charid)
        code, result = common.request_esi.esi(__name__, request_url, method='get', charid=charid, version='v2')

        if not code == 200:
            # it doesn't really matter
            msg = '{0} API error: {1}'.format(request_url, result)
            logger.debug(msg)
            location = None
            chardetails['online'] = 'Unknown'
            chardetails['last_online'] = 'Unknown'
        else:
            chardetails['online'] = result['online']
            chardetails['last_online'] = result['last_login']
        try:
            request_url_corp = 'corporations/{0}/'.format(corp_id)
            code_corp, result_corp = common.request_esi.esi(__name__, request_url_corp, 'get')

            if not code_corp == 200:
                _logger.log('[' + __name__ + '] /corporations API error {0}: {1}'.format(code_corp, result_corp['error']), _logger.LogLevel.WARNING)
                msg = '{0} API error: {1}'.format(request_url_corp, result)
                logger.warning(msg)
            else:
                chardetails['corporation'] = result_corp['name']
        except KeyError as error:
            msg = 'corporation id does not exist: {0}'.format(corp_id)
            logger.error(msg)
            charname = None
    return chardetails
Exemple #5
0
def core_srp_requests_past(charid):

    logger = getlogger('core.srp.post')

    ipaddress = request.args.get('log_ip')
    if ipaddress is None:
        ipaddress = request.headers['X-Real-Ip']

    securitylog('SRP post', ipaddress=ipaddress, charid=charid)

    kill_url = request.form['url']
    fleetfc = request.form['fleetfc']
    notes = request.form['notes']

    # only people in tri can get SRP

    affiliations = _esihelpers.esi_affiliations(charid)

    if affiliations['allianceid'] != 933731581:
        response = {'error': 'not eligible for SRP'}
        return Response(json.dumps(response),
                        status=401,
                        mimetype='application/json')

    # use regex to get the zkill kill ID

    pattern = re.compile('(.*)zkillboard.com/kill/(\d+)(.*)')
    match = re.match(pattern, kill_url)
    if match:
        killid = match.group(2)
    else:
        response = {'error': 'unable to parse zkill url: {0}'.format(kill_url)}
        return Response(json.dumps(response),
                        status=400,
                        mimetype='application/json')

    # fetch zkill data

    request_url = 'killID/{}/'.format(killid)
    code, result = _esi.esi(__name__, request_url, base='zkill')

    try:
        if result.get('error'):
            msg = 'zkill error {0}: {1}'.format(request_url, result['error'])
            logger.error(msg)
            response = {'error': msg}
            return Response(json.dumps(response),
                            status=400,
                            mimetype='application/json')
    except Exception as e:
        # f****d up zkill api spews different datatypes and doesn't use http return codes right
        pass

    killhash = result[0]['zkb']['hash']
    value = result[0]['zkb']['totalValue']

    # fetch actual kill data from ESI

    request_url = 'killmails/{0}/{1}/'.format(killid, killhash)
    code, result = _esi.esi(__name__, request_url, version='v1')

    if code != 200:
        msg = 'ESI error {0}: {1}'.format(request_url, result)
        response = {'error': msg}
        logger.error(msg)
        return Response(json.dumps(response),
                        status=500,
                        mimetype='application/json')

    killtime = result['killmail_time']
    victim = result['victim']
    shipid = result['victim']['ship_type_id']
    victimid = result['victim']['character_id']
    victim = _esihelpers.esi_affiliations(victimid)

    request_url = 'universe/types/{}/'.format(shipid)
    code, result = _esi.esi(__name__, request_url)

    if code != 200:
        msg = 'ESI error {0}: {1}'.format(request_url, result)
        response = {'error': msg}
        logger.error(msg)
        return Response(json.dumps(response),
                        status=500,
                        mimetype='application/json')

    shipname = result['name']

    killtime = killtime.replace('T', ' ')
    killtime = killtime.replace('Z', '')

    # start doing db checks

    try:
        sql_conn = mysql.connect(database=_database.DB_DATABASE,
                                 user=_database.DB_USERNAME,
                                 password=_database.DB_PASSWORD,
                                 host=_database.DB_HOST)

    except mysql.Error as err:
        msg = 'mysql error: {0}'.format(err)
        logger.error(msg)
        js = json.dumps({'error': msg})
        resp = Response(js, status=500, mimetype='application/json')
        return resp

    cursor = sql_conn.cursor()

    # check that there is no duplicate killmail

    query = 'SELECT killID from SRP where killID=%s'
    try:
        rowcount = cursor.execute(query, (killid, ))
    except mysql.Error as err:
        msg = 'mysql error: {0}'.format(err)
        logger.error(msg)
        js = json.dumps({'error': msg})
        cursor.close()
        return Response(js, status=500, mimetype='application/json')

    if rowcount > 0:
        msg = 'kill ID {0} already submitted for SRP'.format(killid)
        js = json.dumps({'error': msg})
        cursor.close()
        return Response(js, status=400, mimetype='application/json')

    # fetch estimated payout
    query = 'SELECT value FROM CalcSRP WHERE shipTypeID=%s'
    try:
        rowcount = cursor.execute(query, (shipid, ))
        rows = cursor.fetchall()
    except mysql.Error as err:
        msg = 'mysql error: {0}'.format(err)
        logger.error(msg)
        js = json.dumps({'error': msg})
        cursor.close()
        return Response(js, status=500, mimetype='application/json')

    if rowcount == 0:
        msg = 'ship type {0} not eligible for SRP'.format(shipname)
        js = json.dumps({'error': msg})
        cursor.close()
        return Response(js, status=400, mimetype='application/json')

    payout, = rows
    payout = payout[0]

    # map the charid to a charnme for payments

    paychar = _esihelpers.esi_affiliations(charid)
    paychar = paychar['charname']

    # insert data into SRP table

    query = 'INSERT into SRP (RequestTime, RequestedByCharID, LossTime, Shiptype, shipTypeID, charID, '
    query += 'charName, zkbLink, killID, srpStatus, payChar, fleetFC, estPayout, obs)'
    query += ' VALUES (FROM_UNIXTIME(%s), %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)'
    try:
        result = cursor.execute(
            query, (time.time(), charid, killtime, shipname, shipid, victimid,
                    victim['charname'], kill_url, killid, 0, paychar, fleetfc,
                    payout, notes))
    except mysql.Error as err:
        msg = 'mysql error inserting SRP: {0}'.format(err)
        logger.error(msg)
        js = json.dumps({'error': msg})
        cursor.close()
        return Response(js, status=500, mimetype='application/json')

    sql_conn.commit()
    cursor.close()

    return Response({}, status=200, mimetype='application/json')
Exemple #6
0
def registeruser(charid,
                 atoken,
                 rtoken,
                 isalt=False,
                 altof=None,
                 tempblue=False,
                 renter=False):
    # put the barest skeleton of information into ldap/mysql

    logger = getlogger('core.sso.registeruser')

    # get character affiliations

    headers = {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
    }

    if isalt:
        msg = 'registering user {0} (alt of {1})'.format(charid, altof)
    else:
        msg = 'registering user {}'.format(charid)

    logger.info(msg)

    # affiliations

    affiliations = _esihelpers.esi_affiliations(charid)
    charname = affiliations.get('charname')
    corpid = affiliations.get('corpid')
    corpname = affiliations.get('corporation_name')
    allianceid = affiliations.get('allianceid')
    alliancename = affiliations.get('alliancename')

    # sort out basic auth groups

    if tempblue:
        # default level of access for vanguard blues
        authgroups = ['public', 'vanguardBlue']
        accountstatus = 'blue'
    if renter:
        # renter groups
        authgroups = ['public', 'renters']
        accountstatus = 'public'
    else:
        # default level of access
        authgroups = ['public']
        accountstatus = 'public'

    if allianceid == 933731581:
        # tri specific authgroup
        authgroups.append('triumvirate')
        accountstatus = 'blue'

    # setup the service user/pass

    cn, dn = _ldaphelpers.ldap_normalize_charname(charname)
    dn = "cn={},ou=People,dc=triumvirate,dc=rocks".format(cn)

    # create the stub

    result, code = _ldaphelpers.ldap_create_stub(charid=charid,
                                                 charname=charname,
                                                 isalt=isalt,
                                                 altof=altof,
                                                 accountstatus=accountstatus,
                                                 authgroups=authgroups,
                                                 rtoken=rtoken,
                                                 atoken=atoken)

    #    if result:
    #
    #        if isalt:
    #            msg = 'new user {0} (alt of {1} registered'.format(charname, altof)
    #        else:
    #            msg = 'new user {0} registered'.format(charname)

    return
Exemple #7
0
def user_audit(dn, details):

    if dn == 'ou=People,dc=triumvirate,dc=rocks':
        return

    logger = logging.getLogger('audit.core.user')

    msg = 'auditing user: {0}'.format(dn)
    logger.debug(msg)

    # groups that a non-blue user is allowed to have

    safegroups = set([ 'public', 'ban_pending', 'banned', ])

    if details['uid'] == None:
        logger.error('dn {0} has no charid'.format(dn))
        return False

    charid = int(details['uid'])
    status = details['accountStatus']
    altof = details['altOf']
    charname = details['characterName']
    raw_groups = details['authGroup']
    esi_rtoken = details['esiRefreshToken']
    discord_rtoken = details['discordRefreshToken']
    ldap_discorduid = details['discorduid']
    ldap_discord2fa = details['discord2fa']
    ldap_discordname = details['discordName']
    ldap_scopes = details['esiScope']
    ldap_roles = details['corporationRole']

    if ldap_scopes is None:
        ldap_scopes = []
    if ldap_roles is None:
        ldap_roles = []

    if details['lastLogin'] is not None:
        ldap_lastlogin = float(details['lastLogin'])
    else:
        ldap_lastlogin = 0

    # we do not need to keep entries on people where there's no meaningful data

    purged = purge(dn, details)

    if purged:
        # this has been purged, we're done here.
        msg = 'successfully purged dn {0}'.format(dn)
        logger.debug(msg)
        return

    ## bugfix sanity checks
    # there was once a bug where people would be marked as alts of themselves
    # this results in interesting effects

    if altof == charid:
        _ldaphelpers.update_singlevalue(dn, 'altOf', None)

    # there was once a time where tokens were purged but their associated scopes and roles were not

    if esi_rtoken is None:

        if ldap_scopes != []:
            _ldaphelpers.update_singlevalue(dn, 'esiScope', None)
        if ldap_roles != []:
            _ldaphelpers.update_singlevalue(dn, 'corporationRole', None)

    # discord
    # this seems like the best place to put this logic

    if details['discordRefreshToken'] is not None:
        request_url = '/users/@me'
        code, result = common.request_esi.esi(__name__, request_url, method='get', charid=charid, version='v6', base='discord')

        if not code == 200:
            msg = 'unable to get discord user information for {0}: ({1}) {2}'.format(dn, code, result)
            logger.error(msg)
            return False

        discorduid = int(result.get('id'))
        discord2fa = result.get('mfa_enabled')
        discordname = result.get('username')

        # update/set discord UID and username
        if ldap_discorduid is None:
            _ldaphelpers.add_value(dn, 'discorduid', discorduid)
        elif ldap_discorduid != discorduid:
            _ldaphelpers.update_singlevalue(dn, 'discorduid', discorduid)

        if ldap_discordname is None:
            _ldaphelpers.add_value(dn, 'discordName', discordname)
        elif ldap_discordname != discordname:
            _ldaphelpers.update_singlevalue(dn, 'discordName', discordname)

        # update 2fa status

        if ldap_discord2fa is None:
            _ldaphelpers.add_value(dn, 'discord2fa', discord2fa)
        elif ldap_discord2fa != discord2fa:
            _ldaphelpers.update_singlevalue(dn, 'discord2fa', discord2fa)

    if details['esiRefreshToken'] is not None and 'esi-location.read_online.v1' in ldap_scopes and 1 is 2:
        # get online status if possible

        request_url = 'characters/{0}/online/'.format(charid)
        code, result = common.request_esi.esi(__name__, request_url, method='get', charid=charid, version='v2')

        if not code == 200:
            # it doesn't really matter
            msg = 'characters online API error {0}: {1}'.format(code, result)
            logger.error(msg)
        else:
            # example:
            # {'online': False, 'last_login': '******', 'last_logout': '2017-07-11T06:32:41Z', 'logins': 2474}
            last_login = time.strptime(result.get('last_login'), "%Y-%m-%dT%H:%M:%SZ")
            last_login = time.mktime(last_login)

            if ldap_lastlogin != last_login:
                _ldaphelpers.update_singlevalue(dn, 'lastLogin', last_login)
                ldap_lastlogin = last_login
    else:
        # so there's always a valid comparison
        if ldap_lastlogin != 0:
            _ldaphelpers.update_singlevalue(dn, 'lastLogin', 0)

    # affiliations information

    affilliations = _esihelpers.esi_affiliations(charid)

    if affilliations.get('error'):
        return False

    esi_allianceid = affilliations.get('allianceid')
    esi_alliancename = affilliations.get('alliancename')
    esi_corpid = affilliations.get('corpid')
    esi_corpname = affilliations.get('corpname')

    # what ldap thinks

    try:
        ldap_allianceid = int(details.get('alliance'))
    except Exception as e:
        ldap_allianceid = None
    try:
        ldap_corpid = int(details.get('corporation'))
    except Exception as e:
        ldap_corpid = None

    ldap_alliancename = details.get('allianceName')
    ldap_corpname = details.get('corporationName')

    # user's effective managable groups
    eff_groups = list( set(raw_groups) - safegroups )

    # tinker with ldap to account for reality

    if not esi_allianceid == ldap_allianceid:
        # update a changed alliance id
        _ldaphelpers.update_singlevalue(dn, 'alliance', str(esi_allianceid))

    if not esi_alliancename == ldap_alliancename:
        # update a changed alliance name
        _ldaphelpers.update_singlevalue(dn, 'allianceName', str(esi_alliancename))

    if not esi_corpid == ldap_corpid:
        # update a changed corp id
        _ldaphelpers.update_singlevalue(dn, 'corporation', str(esi_corpid))
    if not esi_corpname == ldap_corpname:
        # update a changed corp name
        _ldaphelpers.update_singlevalue(dn, 'corporationName', str(esi_corpname))

    # GROUP MADNESS

    vanguard = vg_alliances() + vg_blues()

    if vanguard == False:
        return

    # NOT banned:

    if 'banned' not in raw_groups and status is not 'banned':

        # non-banned people should all have public
        # not sure how this happens

        if 'public' not in raw_groups:
            _ldaphelpers.add_value(dn, 'authGroup', 'public')

        # this character is blue but not marked as such
        if esi_allianceid in vg_alliances() or esi_allianceid in vg_blues() or esi_allianceid in vg_renters():
            if not status == 'blue':
                _ldaphelpers.update_singlevalue(dn, 'accountStatus', 'blue')

        # pubbies do not need status. this does not affect alts meaningfully.
        if not esi_allianceid in vg_alliances() and esi_allianceid not in vg_blues() and esi_allianceid not in vg_renters():

            if not status == 'public':
                _ldaphelpers.update_singlevalue(dn, 'accountStatus', 'public')

            if len(eff_groups) > 0:
                _ldaphelpers.purge_authgroups(dn, eff_groups)

        triumvirate = 933731581

        # activity purge

        if ldap_lastlogin is None:
            ldap_lastlogin = 0

        login_difference = time.time() - ldap_lastlogin

        if login_difference > 30*86400 and esi_allianceid == triumvirate:
            # logic to purge out of groups based on inactivity
            pass
#            _ldaphelpers.purge_authgroups(dn, eff_groups)
#            return

        # some checks for those marked blue already
        if status == 'blue':

            if 'vanguard' in eff_groups and esi_allianceid in vg_blues():
                # a special case for people moving from vanguard to vg blues
                _ldaphelpers.purge_authgroups(dn, ['vanguard'])

            if 'vanguardBlue' in eff_groups and esi_allianceid in vg_alliances():
                # a special case for people moving from vanguard blues to vanguard
                _ldaphelpers.purge_authgroups(dn, ['vanguardBlue'])

            if 'renters' not in eff_groups:
                if esi_allianceid in vg_renters():
                    # renters get renters
                    _ldaphelpers.add_value(dn, 'authGroup', 'renters')

            if 'triumvirate' not in eff_groups:
                if esi_allianceid == triumvirate:
                    # tri get trimvirate
                    _ldaphelpers.add_value(dn, 'authGroup', 'triumvirate')

            if 'vanguardBlue' not in eff_groups:
                if esi_allianceid in vg_blues():
                    # vanguard blues get this
                    _ldaphelpers.add_value(dn, 'authGroup', 'vanguardBlue')

            if 'vanguard' not in eff_groups:
                if esi_allianceid in vg_alliances():
                    # vanguard alliances get vanguard
                    _ldaphelpers.add_value(dn, 'authGroup', 'vanguard')

    # purge shit from banned people

    if 'banned' in raw_groups:

        # purge off any groups you shouldn't have

        if len(eff_groups) > 0:
            _ldaphelpers.purge_authgroups(dn, eff_groups)

        if not status == 'banned':
            _ldaphelpers.update_singlevalue(dn, 'accountStatus', 'banned')

    if status == 'banned' and 'banned' not in raw_groups:
        # this shouldn't happen but this makes sure data stays synchronized

        # purge off any groups you shouldn't have
        if len(eff_groups) > 0:
            _ldaphelpers.purge_authgroups(dn, eff_groups)
        # make sure banned people don't have pending by accident

        if 'ban_pennding' in raw_groups:
            _ldaphelpers.purge_authgroups(dn, ['ban_pending'])

    return True
Exemple #8
0
def auth_evesso_callback():

    logger = getlogger('core.sso.callback')

    client_id = _eve.client_id
    client_secret = _eve.client_secret
    redirect_url = _eve.redirect_url

    base_url = 'https://login.eveonline.com'
    token_url = base_url + '/v2/oauth/token'

    # the user has (ostensibly) authenticated with the application, now
    # the access token can be fetched

    altof = session.get('altof')
    isalt = session.get('isalt')
    tempblue = session.get('tempblue')
    renter = session.get('renter')
    state = session.get('oauth2_state')
    ipaddress = request.headers['X-Real-Ip']

    # security logging

    if isalt == True:
        detail = 'alt of {}'.format(altof)
        auth_scopes = scope
    elif tempblue == True:
        detail = 'temp blue'
        # make sure we only check for the blue scope list
        auth_scopes = blue_scope
    elif renter == True:
        detail = 'renter'
        auth_scopes = renter_scope
    else:
        detail = None
        auth_scopes = scope

    action = 'SSO callback'
    securitylog(action=action, ipaddress=ipaddress, detail=detail)

    # handle oauth token manipulation

    oauth_session = OAuth2Session(
        client_id=client_id,
        state=state,
        redirect_uri=redirect_url,
        auto_refresh_kwargs={
            'client_id': client_id,
            'client_secret': client_secret,
        },
        auto_refresh_url=token_url,
    )

    headers = {'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' }
    try:
        atoken = oauth_session.fetch_token(
            token_url,
            client_secret=client_secret,
            authorization_response=request.url,
            headers=headers,
        )

    except Exception as error:
        msg = 'unable to fetch eve sso access token: {0}'.format(error)
        logger.error(msg)
        return('ERROR: ' + str(error))

    access_token = atoken['access_token']
    refresh_token = atoken['refresh_token']
    expires_at = atoken['expires_at']

    try:
        charid, charname, scopes = verify(access_token)
    except Exception as e:
        # this ought to never happen
        msg = 'unable to verify eve sso access token: {0}'.format(error)
        logger.error(msg)
        message = 'SORRY, internal error. Try again.'
        response = make_response(message)
        return response

    # full ESI affiliations

    affilliations = _esihelpers.esi_affiliations(charid)

    if affilliations.get('error'):
        msg = 'error in fetching affiliations for {0}: {1}'.format(charid, affilliations.get('error'))
        logger.error(msg)
        message = 'SORRY, internal error. Try again.'
        response = make_response(message)
        return response

    allianceid = affilliations.get('allianceid')
    alliancename = affilliations.get('alliancename')
    corpid = affilliations.get('corpid')

    # ldap, if any

    userinfo = _ldaphelpers.ldap_userinfo(charid)

    # get alt status, if any, from ldap
    if userinfo and not isalt:
        altof = userinfo.get('altOf')

        if altof is not None:
            isalt = True

    # what the f**k is going on
    # this is a check that _shouldnt_ trigger anymore

    if isalt:
        if altof == None or altof == 'None':
            msg = 'is an alt but altof = None? wtf. charid {0} altof {1} {2}'.format(charid, altof, type(altof))
            logger.error(msg)
            msg = 'error in fetching alt information. please poke saeka.'
            response = make_response(msg)
            return response

    # fix authgroup to an empty array in case nothing

    if not userinfo:
        authgroups = []
    else:
        authgroups = userinfo.get('authGroup')
        if authgroups is None:
            authgroups = []

    # verify that the atoken we get actually has the correct scopes that we requested
    # just in case someone got cute and peeled some off.

    if not check_scope(charid, auth_scopes, atoken=access_token):
        # the user peeled something off the scope list. naughty.
        msg = 'user {0} modified scope list'.format(charid)
        logger.warning(msg)

        securitylog(action='core login scope modification', charid=charid, ipaddress=ipaddress)

        message = "Don't tinker with the scope list, please.<br>"
        message += "If you have an issue with it, talk to triumvirate leadership."
        response = make_response(message)
        return response
    else:
        # scopes validate
        msg = 'user {0} has expected scopes'.format(charid)
        logger.debug(msg)
        # register the user, store the tokens
        registeruser(charid, access_token, refresh_token, tempblue=tempblue, isalt=isalt, altof=altof, renter=renter)
        storetokens(charid, access_token, refresh_token, expires=expires_at)


    ## TESTS
    ##
    ## check affiliations and for bans

    # check to see if the user is banned

    if 'banned' in authgroups:
        # banned users not allowed under any conditions
        message = 'nope.avi'
        if isalt == True:
            msg = 'banned user {0} ({1}) tried to register alt {2}'.format(charid, charname, altof)
            logger.warning(msg)
            securitylog(action='banned user tried to register', charid=charid, ipaddress=ipaddress, detail='alt of {0}'.format(altof))
        else:
            msg = 'banned user {0} ({1}) tried to register'.format(charid, charname)
            logger.warning(msg)
            securitylog(action='banned user tried to register', charid=charid, ipaddress=ipaddress)
        return make_response(message)


    # only tri & blues are allowed to use auth
    if allianceid not in vg_blues() and allianceid not in vg_alliances() and allianceid not in vg_renters():
        if not isalt:
            # not an alt, not a blue. not a renter. go away.
            msg = 'please contact a recruiter if you are interested in joining triumvirate'
            logmsg = 'non-blue user {0} ({1}) tried to register'.format(charid, charname)
            logger.warning(logmsg)
            securitylog(action='non-blue user tried to register', charid=charid, ipaddress=ipaddress)
            return make_response(msg)
        else:
            # someone is registering a non-blue alt, nbd
            pass

    # make sure the temp blue endpoint not being used by tri proper
    if tempblue:
        # this is a tri blue, but not tri proper.
        # ...or at least ought to be.
        if allianceid in vg_alliances():
            # naughty! but not worth logging
            msg = 'please use the other login endpoint. <br>'
            msg += 'this is a lower privileged one for blues <b>ONLY</b>'
            return make_response(msg)

    # is this a temp blue trying to login with the wrong endpoint?
    if allianceid in vg_blues():
        if not tempblue:
            # no big deal. we got extra scopes for it.
            tempblue = True

    # the user has passed the various exclusions, gg

    # security logging

    action = 'SSO callback completed'
    detail = None
    if isalt == True:
        detail='alt of {0}'.format(altof)
    elif tempblue == True:
        detail='blue from {0}'.format(alliancename)
    elif renter == True:
        detail='renter from {0}'.format(alliancename)

    securitylog(action=action, charid=charid, ipaddress=ipaddress, detail=detail)

    expire_date = datetime.datetime.now() + datetime.timedelta(days=14)

    # build the cookie and construct the http response

    if isalt == True:
        # if the character being logged in is an alt, make a session for the main.

        if userinfo:
            # the alt is alredy registered. go to homepage.
            response = make_response(redirect('https://www.triumvirate.rocks'))
        else:
            # go to alt registration page to show update.
            response = make_response(redirect('https://www.triumvirate.rocks/altregistration'))

        cookie = _session.makesession(altof)
        msg = 'created session for user: {0} (alt of {1})'.format(charname, altof)
        logger.info(msg)

    else:
        # proceed normally otherwise
        response = make_response(redirect('https://www.triumvirate.rocks'))
        cookie = _session.makesession(charid)
        msg = 'created session for user: {0} (charid {1})'.format(charname, charid)
        logger.info(msg)

    response.set_cookie('tri_core', cookie, domain='.triumvirate.rocks', expires=expire_date)

    if cookie == False:
        # unable to construct session cookie
        msg = 'error in creating session cookie for user {0}'.format(charid)
        logger.error(msg)
        message = 'SORRY, internal error. Try again.'
        return make_response(message)

    # handle registered users

    if userinfo is not None:
        # already in ldap, and not banned

        action = 'core login'
        if isalt:
            # is a registered alt
            msg = 'alt user {0} (alt of {1}) already registered'.format(charname, altof)
            logger.info(msg)
            securitylog(action=action, charid=charid, ipaddress=ipaddress, detail='via alt {0}'.format(altof))
            code, result = _ldaphelpers.ldap_altupdate(__name__, altof, charid)
            return response
        else:
            if tempblue:
                # registered blue main.
                msg = 'user {0} ({1}) already registered'.format(charid, charname)
                logger.info(msg)
                securitylog(action=action, charid=charid, ipaddress=ipaddress, detail='blue from {0}'.format(alliancename))
                code, result = _ldaphelpers.ldap_altupdate(__name__, altof, charid)
                return response
            else:
                # registered character
                msg = 'user {0} ({1}) already registered'.format(charid, charname)
                logger.info(msg)
                securitylog(action=action, charid=charid, ipaddress=ipaddress)
                code, result = _ldaphelpers.ldap_altupdate(__name__, altof, charid)
                return response

    # after this point, the only folks that are left are unregistered users

    # handle new temp blues
    if tempblue:
        msg = 'user {0} ({1}) not registered'.format(charid, charname)
        logger.info(msg)
        securitylog(action='core user registered', charid=charid, ipaddress=ipaddress, detail='blue from {0}'.format(alliancename))
        return response

    # handle new renters
    if renter:
        msg = 'user {0} ({1}) not registered'.format(charid, charname)
        logger.info(msg)
        securitylog(action='core user registered', charid=charid, ipaddress=ipaddress, detail='renter from {0}'.format(alliancename))
        return response

    # handle new alts

    if isalt:
        msg = 'alt user {0} (alt of {1}) not registered'.format(charname, altof)
        logger.info(msg)
        securitylog(action='alt user registered', charid=charid, ipaddress=ipaddress, detail='alt of {0}'.format(altof))
        return response
    else:
        msg = 'user {0} ({1}) not registered'.format(charid, charname)
        logger.info(msg)
        securitylog(action='core user registered', charid=charid, ipaddress=ipaddress)
        return response
Exemple #9
0
def ldap_create_stub(
    charname=None, charid=None, isalt=False,
    altof=None, accountstatus='public', authgroups=['public'],
    atoken=None, rtoken=None
    ):


    # make a very basic ldap entry for charname

    function = __name__

    user = dict()

    if charname is not None and charid is None:
        # making a stub based on a charid
        result = _esihelpers.user_search(charname)

        if result == False:
            # damage
            msg = 'ESI search error'
            return False, msg
        elif len(result) is 0:
            # nothing found.
            msg = 'no such character'
            return False, msg
        elif len(result) > 1:
            # too much found
            msg = 'too many results'
            return False, msg

        # okay hopefuly nothing else f****d up by now

        charid = result['character']

    if charid is None:
        msg = 'no charname, no charid'
        return False, msg

    # get affiliations and shit

    affiliations = _esihelpers.esi_affiliations(charid)

    charname = affiliations.get('charname')
    corpid = affiliations.get('corpid')
    corpname = affiliations.get('corporation_name')
    allianceid = affiliations.get('allianceid')
    alliancename = affiliations.get('alliancename')

    cn, dn = ldap_normalize_charname(charname)

    user['uid'] = charid
    user['altof'] = altof
    user['characterName'] = charname
    user['corporation'] = corpid

    if allianceid:
        user['alliance'] = allianceid

    user['cn'] = cn
    user['sn'] = cn
    user['accountStatus'] = accountstatus
    user['authGroup'] = authgroups

    if rtoken:
        user['esiAccessToken'] = atoken
        user['esiRefreshToken'] = rtoken

    # build the stub

    attrs = []

    # generate a bullshit password

    password = uuid.uuid4().hex
    password_hash = ldap_salted_sha1.hash(password)

    user['userPassword'] = password_hash

    # handle rest of crap

    for item in user.keys():

        # authgroup is a repeated attribute so has to be handled carefully

        if item == 'authGroup':
            newgroups = []
            for group in authgroups:
                group = str(group).encode('utf-8')
                newgroups.append(group)
            user['authGroup'] = newgroups
        else:
            user[item] = [ str(user[item]).encode('utf-8') ]
        attrs.append((item, user[item]))

    attrs.append(('objectClass', ['top'.encode('utf-8'), 'pilot'.encode('utf-8'), 'simpleSecurityObject'.encode('utf-8'), 'organizationalPerson'.encode('utf-8')]))

    # ldap binding
    ldap_conn = ldap.initialize(_ldap.ldap_host, bytes_mode=False)
    try:
        ldap_conn.simple_bind_s(_ldap.admin_dn, _ldap.admin_dn_password)
    except ldap.LDAPError as error:
        _logger.log('[' + __name__ + '] LDAP connection error: {}'.format(error),_logger.LogLevel.ERROR)
        return False, error

    # add the stub to ldap

    try:
        ldap_conn.add_s(dn, attrs)
    except Exception as error:
        return False, error
    finally:
        ldap_conn.unbind()

    # no error. done!

    return True, dn
Exemple #10
0
def securitylog(function,
                action,
                charid=None,
                charname=None,
                ipaddress=None,
                date=None,
                detail=None):
    # log stuff into the security table

    import MySQLdb as mysql
    import common.credentials.database as _database
    import common.esihelpers as _esihelpers
    import common.logger as _logger
    import common.request_esi
    import time
    import urllib

    if date == None:
        date = time.time()

    friendly_time = time.asctime(time.localtime(date))

    # mysql logging

    try:
        sql_conn = mysql.connect(database=_database.DB_DATABASE,
                                 user=_database.DB_USERNAME,
                                 password=_database.DB_PASSWORD,
                                 host=_database.DB_HOST)
    except mysql.Error as err:
        _logger.log('[' + __name__ + '] mysql error: ' + str(err),
                    _logger.LogLevel.ERROR)
    cursor = sql_conn.cursor()

    # try to get character id if a charname (but no charid) is supplied

    if not charname == None and charid == None:
        query = {
            'categories': 'character',
            'language': 'en-us',
            'search': charname,
            'strict': 'true'
        }
        query = urllib.parse.urlencode(query)
        esi_url = 'search/?' + query
        code, result = common.request_esi.esi(__name__, esi_url, 'get')

        # not going to hardfail here, search is fuzzier than other endpoints

        try:
            charid = result['character'][0]
        except KeyError as error:
            _logger.log(
                '[' + function +
                '] unable to identify charname: {0}'.format(charname),
                _logger.LogLevel.WARNING)
            charid = None

    # try to get character name if a charid (but no charname) is supplied
    if charname == None and not charid == None:
        affiliations = _esihelpers.esi_affiliations(charid)
        charname = affiliations.get('charname')

    # log to file

    _logger.log(
        '[{0}] {1}: {2} ({3}) @ {4} {5}: {6}'.format(function, friendly_time,
                                                     charname, charid,
                                                     ipaddress, action,
                                                     detail),
        _logger.LogLevel.INFO)

    # log to security table

    try:
        query = 'INSERT INTO Security (charID, charName, IP, action, date, detail) VALUES(%s, %s, %s, %s, FROM_UNIXTIME(%s), %s)'
        cursor.execute(
            query,
            (
                charid,
                charname,
                ipaddress,
                action,
                date,
                detail,
            ),
        )
    except mysql.Error as err:
        _logger.log('[' + __name__ + '] mysql error: ' + str(err),
                    _logger.LogLevel.ERROR)
        return False
    finally:
        cursor.close()
        sql_conn.commit()
        sql_conn.close()
    return True
Exemple #11
0
def notification_process(not_type, not_data, charid=None):

    # figure out what additional information needs to be requested from ESI or whatnot
    # in order to make these f*****g notifications make sense
    # f**k me so much crap

    data = {}

    # pocos if we ever give a shit: OrbitalReinforced / OrbitalAttacked

    if not_type == 'StructureLostShields':
        # example:
        # {'solarsystemID': 30001154, 'structureID': 1023478424724, 'timeLeft': 837701544696,
        #'structureShowInfoData': ['showinfo', 35832, 1023478424724], 'vulnerableTime': 9000000000}
        pass

    if not_type == 'StructureLostArmor':
        # same as StructureLostShields
        pass

    if not_type == 'StructureUnderAttack':
        # example:
        # {'allianceLinkData': ['showinfo', 16159, 498125261], 'shieldPercentage': 2.7245399818695597e-10, 'hullPercentage': 100.0,
        # 'solarsystemID': 30001154, 'structureShowInfoData': ['showinfo', 35832, 1023478424724], 'corpLinkData': ['showinfo', 2, 98210135],
        #'corpName': 'Infinite Point', 'structureID': 1023478424724, 'charID': 96554255, 'allianceID': 498125261,
        #'armorPercentage': 99.95246052415511, 'allianceName': 'Test Alliance Please Ignore'}

        corpLinkData = not_data.get('corpLinkData')

        # to keep compatable with the general method later

        data['status'] = round(not_data.get('shieldPercentage'), 2), round(
            not_data.get('armorPercentage'),
            2), round(not_data.get('hullPercentage'), 2)
        data['attacker_affiliations'] = _esihelpers.esi_affiliations(
            not_data.get('charID'))

    if not_type == 'TowerAlertMsg':
        # example:
        # {'moonID': 40139259, 'shieldValue': 0.9999255519999989, 'aggressorCorpID': 803493697, 'aggressorID': 90936488,
        #'solarSystemID': 30002185, 'aggressorAllianceID': 498125261, 'typeID': 16214, 'hullValue': 1.0, 'armorValue': 1.0}
        data['moon_info'] = _esihelpers.moon_info(not_data.get('moonID'))
        data['attacker_affiliations'] = _esihelpers.esi_affiliations(
            not_data.get('aggressorID'))
        data['status'] = round(100 * not_data.get('shieldValue'), 2), 100, 100

    if not_type == 'SovCommandNodeEventStarted':
        # example:
        # {'constellationID': 20000347, 'solarSystemID': 30002365, 'campaignEventType': 1}
        data['campaign_typeid'] = not_data.get('campaignEventType')
        data['campaign_type'] = sov_campaigns(
            not_data.get('campaignEventType'))

    if not_type == 'EntosisCaptureStarted':
        # example:
        # {'solarSystemID': 30000744, 'structureTypeID': 21646}
        pass

    if not_type == 'SovStructureDestroyed':
        # example:
        # {'solarSystemID': 30000825, 'structureTypeID': 32226}
        pass

    # common things

    # structure info

    if not_data.get('structureShowInfoData') is not None:
        data['structure_type_data'] = _esihelpers.type_info(
            not_data.get('structureShowInfoData')[1])

    if not_data.get('structureTypeID') is not None:
        data['structure_type_data'] = _esihelpers.type_info(
            not_data.get('structureTypeID'))

    # structure ID for specific queries

    if not_data.get('structureID') is not None:
        data['structure_id'] = not_data.get('structureID')

        # fetch structure name
        request_url = 'universe/structures/{0}/'.format(data['structure_id'])
        code, result = common.request_esi.esi(__name__,
                                              request_url,
                                              version='v2',
                                              charid=charid)

        data['structure_name'] = result.get('name')

        # can get solar system id from corplinkdata but whatever
        data['solar_system_info'] = _esihelpers.solar_system_info(
            result['solar_system_id'])

    # system info

    if not_data.get('solarSystemID') is not None:
        data['solar_system_info'] = _esihelpers.solar_system_info(
            not_data.get('solarSystemID'))

    return data