Ejemplo n.º 1
0
def migratefailures():

    try:
        sql_conn = mysql.connect(database='forum',
                                 user='******',
                                 password='******',
                                 host=_database.DB_HOST)
    except mysql.Error as err:
        return False

    cursor = sql_conn.cursor()

    query = 'select name, failed_logins from forum.core_members'

    try:
        rowcount = cursor.execute(query)
        rows = cursor.fetchall()
    except mysql.Error as err:
        return False

    for charname, failed_logins in rows:

        if failed_logins == None or failed_logins == '[]':
            continue

        failures = json.loads(failed_logins)
        for ip_address in failures.keys():
            for date in failures[ip_address]:
                _logger.securitylog(__name__,
                                    'forum login',
                                    charname=charname,
                                    ipaddress=ip_address,
                                    date=date,
                                    detail='invalid password')
Ejemplo n.º 2
0
def evesso(isalt=False, altof=None, tempblue=False, renter=False):

    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'
    base_auth_url = base_url + '/v2/oauth/authorize'

    # security logging

    ipaddress = request.headers['X-Real-Ip']
    detail = None
    if isalt == True:
        detail='alt of {}'.format(altof)
        auth_scopes = scope
    elif tempblue == True:
        detail='temp blue'
        # the scope list for temp blues is very short
        auth_scopes = blue_scope
    elif renter == True:
        detail='renter'
        # the renter scope list is distinct
        auth_scopes = renter_scope
    else:
        auth_scopes = scope

    securitylog(action='SSO login initiated', ipaddress=ipaddress, detail=detail)

    # setup the redirect url for the first stage of oauth flow

    oauth_session = OAuth2Session(
        client_id=client_id,
        scope=auth_scopes,
        redirect_uri=redirect_url,
        auto_refresh_kwargs={
            'client_id': client_id,
            'client_secret': client_secret,
        },
        auto_refresh_url=token_url,
    )
    auth_url, state = oauth_session.authorization_url(
        base_auth_url,
        isalt=isalt,
        altof=altof,
        renter=renter,
        tempblue=tempblue,
    )

    # store useful parameters in oauth state

    session['tempblue'] = tempblue
    session['oauth2_state'] = state
    session['isalt'] = isalt
    session['altof'] = altof
    session['renter'] = renter

    return redirect(auth_url, code=302)
Ejemplo n.º 3
0
def fleet_vote(fleet_id):

    # get the fleet composition voting results

    r = redis.StrictRedis(host='localhost', port=6379, db=0)
    ipaddress = request.headers['X-Real-Ip']
    log_charid = request.args.get('log_charid')
    _logger.securitylog(__name__,
                        'retrieved fleet vote',
                        detail='fleet {0}'.format(fleet_id),
                        ipaddress=ipaddress,
                        charid=log_charid)

    # redis does not actually connect above, i have to specifically test

    error = False
    try:
        r.client_list()
    except redis.exceptions.ConnectionError as err:
        msg = 'Redis connection error: {0}'.format(err)
        error = True
    except redis.exceptions.ConnectionRefusedError as err:
        msg = 'Redis server offline'
        error = True
    except Exception as err:
        msg = 'Redis generic error: {0}'.format(err)
        error = True

    if error:
        _logger.log('[' + __name__ + '] {0}'.format(msg),
                    _logger.LogLevel.ERROR)
        js = json.dumps({'error': msg})
        return Response(js, status=500, mimetype='application/json')

    # build the data structure back

    result = dict()

    for ship_class in vote_classes:

        # fetch the redis amount
        try:
            amount = r.get('{0}:vote:{1}'.format(fleet_id, ship_class))
        except Exception as e:
            msg = 'error fetching vote amounts from redis'
            js = json.dumps({'error': msg})
            return Response(js, status=500, mimetype='application/json')

        if not amount:
            amount = 0

        result[ship_class] = int(amount)

    js = json.dumps(result)
    return Response(js, status=200, mimetype='application/json')
Ejemplo n.º 4
0
def core_blacklist_remove(charid):

    # demote someone from the blacklist

    # logging
    logger = logging.getLogger('tri_api.endpoints.blacklist.remove')

    ipaddress = request.args.get('log_ip')
    log_charid = request.args.get('charid')

    # optional charid override for command line tinkering to record correctly

    securitylog('blacklist removal',
                ipaddress=ipaddress,
                charid=log_charid,
                detail='charid {0}'.format(charid))

    # fetch details

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

    if code == False:
        msg = 'unable to fetch ldap information: {}'.format(error)
        logger.error(msg)
        js = json.dumps({'error': msg})
        return Response(js, status=500, mimetype='application/json')

    if result == None:
        msg = 'charid {0} is not in core'.format(charid)
        logger.error(msg)
        js = json.dumps({'error': msg})
        return Response(js, status=500, mimetype='application/json')

    for dn, info in result.items():

        purge = [
            'banApprovedBy', 'banApprovedOn', 'banDate', 'banReason',
            'banReportedBy', 'banDescription'
        ]

        _ldaphelpers.update_singlevalue(dn, 'accountStatus', 'public')
        _ldaphelpers.update_singlevalue(dn, 'authGroup', 'public')
        #_ldaphelpers.purge_authgroups(dn, [ 'banned', 'ban_pending' ])
        for attr in purge:
            _ldaphelpers.update_singlevalue(dn, attr, None)

    return Response({}, status=200, mimetype='application/json')
Ejemplo n.º 5
0
def core_corp_broadcast(corpid):

    from flask import request, json, Response
    import common.logger as _logger
    from tri_core.common.broadcast import broadcast
    ipaddress = 'automated'
    message = request.get_data()
    message = message.decode('utf-8')

    # spew at a specific corpid
    if request.method == 'POST':
        _logger.securitylog(__name__,
                            'broadcast',
                            detail='corpid {0}'.format(corpid),
                            ipaddress=ipaddress)
        broadcast(message, corpid=corpid)
        return Response({}, status=200, mimetype='application/json')
Ejemplo n.º 6
0
def alt_remove(main_charid, alt_charid):
    from flask import Response
    from json import dumps
    import common.logger as _logger
    import common.ldaphelpers as _ldaphelpers

    # verify that the alt exists and that it has the right main

    dn = 'ou=People,dc=triumvirate,dc=rocks'
    filterstr = '(&(uid={0})(altOf={1}))'.format(alt_charid, main_charid)
    attributes = ['uid']
    code, result = _ldaphelpers.ldap_search(__name__, dn, filterstr,
                                            attributes)

    if code == False:
        msg = 'unable to connect to ldap: {}'.format(result)
        _logger.log('[' + __name__ + '] {}'.format(msg),
                    _logger.LogLevel.ERROR)
        js = dumps({'error': msg})
        return Response(js, status=500, mimetype='application/json')

    if result == None:
        msg = 'alt+main combo does not exist'
        _logger.log('[' + __name__ + '] {}'.format(msg),
                    _logger.LogLevel.WARNING)
        js = dumps({'error': msg})
        return Response(js, status=404, mimetype='application/json')

    # security logging

    ipaddress = request.headers['X-Real-Ip']
    _logger.securitylog(__name__,
                        'detatching alt',
                        ipaddress=ipaddress,
                        charid=alt_charid,
                        detail='alt of {0}'.format(main_charid))

    code, result = _ldaphelpers.ldap_altupdate(__name__, None, alt_charid)

    if code == True:
        return Response({}, status=200, mimetype='application/json')
    else:
        msg = 'internal error'
        js = dumps({'error': msg})
        return Response(js, status=500, mimetype='application/json')
Ejemplo n.º 7
0
def core_blacklist_confirmall():

    # logging
    logger = logging.getLogger('tri_api.endpoints.blacklist.confirmall')

    ipaddress = request.args.get('log_ip')
    log_charid = request.args.get('charid')

    securitylog('blacklist bulk confirm',
                ipaddress=ipaddress,
                charid=log_charid,
                detail='EVERYONE')

    # fetch details including main + alts

    dn = 'ou=People,dc=triumvirate,dc=rocks'
    filterstr = 'authGroup=ban_pending'
    attrlist = ['characterName', 'uid', 'altOf', 'authGroup', 'accountStatus']
    code, result = _ldaphelpers.ldap_search(__name__, dn, filterstr, attrlist)

    if code == False:
        msg = 'unable to fetch ldap information: {}'.format(error)
        logger.error(msg)
        js = json.dumps({'error': msg})
        return Response(js, status=500, mimetype='application/json')

    if result == None:
        msg = 'no such charid {0}'.format(ban_charid)
        logger.warning(msg)
        js = json.dumps({'error': msg})
        return Response(js, status=404, mimetype='application/json')

    for dn, info in result.items():
        charid = info['uid']
        msg = 'confirming {0}'.format(charid)
        core_blacklist_confirm(charid)


#        url = "https://api.triumvirate.rocks/core/blacklist/{0}/confirm?charid={1}&ipaddress={2}".format(charid, log_charid, ipaddress)
#        requests.get(url)

# maybe not worth bothering?
    return Response({}, status=200, mimetype='application/json')
Ejemplo n.º 8
0
def core_teamspeak(charid):

    ipaddress = request.headers['X-Real-Ip']
    # remove the TS information from a given char
    if request.method == 'DELETE':
        _logger.securitylog(__name__,
                            'teamspeak identity delete',
                            charid=charid,
                            ipaddress=ipaddress)
        return teamspeak_DELETE(charid)

    # get current TS info for a charid
    if request.method == 'GET':
        return teamspeak_GET(charid)

    # update/make new teamspeak identity
    if request.method == 'POST':
        _logger.securitylog(__name__,
                            'teamspeak identity creation',
                            charid=charid,
                            ipaddress=ipaddress)
        return teamspeak_POST(charid)
Ejemplo n.º 9
0
def core_logger():

    # an endpoint wrapper to drop stuff into the security log
    # this is deliberately very thin

    action = request.values.get('action')
    charid = request.values.get('charid')
    charname = request.values.get('charname')
    ipaddress = request.values.get('ipaddress')
    date = request.values.get('date')
    detail = request.values.get('detail')

    # we do not care about the return code because this failing should not impact the thing that is using it. not yet.

    _logger.securitylog(__name__,
                        action,
                        charid=charid,
                        charname=charname,
                        ipaddress=ipaddress,
                        date=date,
                        detail=detail)

    return Response({}, status=200, mimetype='application/json')
Ejemplo n.º 10
0
def core_group_broadcast(group):

    from flask import request, json, Response
    import common.logger as _logger
    from tri_core.common.broadcast import broadcast

    ipaddress = request.args.get('log_ip')
    charid = request.args.get('charid')

    if ipaddress is None:
        ipaddress = request.headers['X-Real-Ip']

    message = request.get_data()
    message = message.decode('utf-8')

    # spew at a group
    if request.method == 'POST':
        _logger.securitylog(__name__,
                            'broadcast',
                            detail='group {0}'.format(group),
                            ipaddress=ipaddress,
                            charid=charid)
        broadcast(message, group=group)
        return Response({}, status=200, mimetype='application/json')
Ejemplo n.º 11
0
def auth_discord_register():

    from requests_oauthlib import OAuth2Session

    import common.logger as _logger
    import common.credentials.discord as _discord
    from tri_core.common.session import readsession

    # fetch the tri core session so we can tie this to a charid

    cookie = request.cookies.get('tri_core')

    if cookie == None:
        msg = 'You need to be logged into tri core with your main in order to register on discord'
        msg += '<br>'
        msg += 'Try logging into CORE again<br>'
        return make_response(msg)

    payload = readsession(cookie)

    if payload == False:
        msg = 'There was a problem reading your tri core cookie.'
        msg += '<br>'
        msg += 'Try logging into CORE again<br>'
        return make_response(msg)

    charid = payload['charID']
    # security logging

    ipaddress = request.headers['X-Real-Ip']
    _logger.securitylog(__name__,
                        'discord registration initiated',
                        ipaddress=ipaddress,
                        charid=charid)

    client_id = _discord.client_id
    client_secret = _discord.client_secret
    redirect_url = _discord.redirect_url
    base_url = _discord.base_url

    base_auth_url = base_url + '/oauth2/authorize'
    token_url = base_url + '/oauth2/token'

    # setup the redirect url for the first stage of oauth flow
    # sadly none of the other discord scopes are useful
    # https://discordapp.com/developers/docs/reference

    scope = ['identify', 'connections', 'guilds']

    oauth_session = OAuth2Session(
        client_id=client_id,
        scope=scope,
        redirect_uri=redirect_url,
        auto_refresh_kwargs={
            'client_id': client_id,
            'client_secret': client_secret,
        },
        auto_refresh_url=token_url,
    )
    auth_url, state = oauth_session.authorization_url(base_auth_url)
    session['oauth2_state'] = state

    return redirect(auth_url, code=302)
Ejemplo n.º 12
0
def fleet_char_vote(fleet_id, char_id):

    # get the fleet composition voting results from a specific character

    r = redis.StrictRedis(host='localhost', port=6379, db=0)
    ipaddress = request.headers['X-Real-Ip']

    # redis does not actually connect above, i have to specifically test

    error = False
    try:
        r.client_list()
    except redis.exceptions.ConnectionError as err:
        msg = 'Redis connection error: {0}'.format(err)
        error = True
    except redis.exceptions.ConnectionRefusedError as err:
        msg = 'Redis server offline'
        error = True
    except Exception as err:
        msg = 'Redis generic error: {0}'.format(err)
        error = True

    if error:
        _logger.log('[' + __name__ + '] {0}'.format(msg),
                    _logger.LogLevel.ERROR)
        js = json.dumps({'error': msg})
        return Response(js, status=500, mimetype='application/json')

    if request.method == 'GET':

        # build the data structure back

        result = dict()

        for ship_class in vote_classes:

            # fetch the redis amount
            try:
                amount = r.get('{0}:vote:{1}:{2}'.format(
                    fleet_id, char_id, ship_class))
            except Exception as e:
                msg = 'error fetching vote amounts from redis: {0}'.format(e)
                _logger.log('[' + __name__ + '] {0}'.format(msg),
                            _logger.LogLevel.ERROR)
                js = json.dumps({'error': msg})
                return Response(js, status=500, mimetype='application/json')
            if not amount:
                amount = 0

            result[ship_class] = int(amount)

        js = json.dumps(result)
        return Response(js, status=200, mimetype='application/json')
    elif request.method == 'POST':

        detailstring = 'fleet {0} vote: '.format(fleet_id)

        for ship_class in vote_classes:
            new_amount = request.values.get(ship_class)
            if not new_amount:
                new_amount = 0

            new_amount = int(new_amount)

            detailstring += '{0}: {1}, '.format(ship_class, new_amount)

            # first set the character vote and overall vote to zero if no keys exist
            r.setnx('{0}:vote:{1}:{2}'.format(fleet_id, char_id, ship_class),
                    0)
            r.setnx('{0}:vote:{1}'.format(fleet_id, ship_class), 0)

            # expiration
            r.expire('{0}:vote:{1}:{2}'.format(fleet_id, char_id, ship_class),
                     86400 * 7)
            r.expire('{0}:vote:{1}'.format(fleet_id, char_id), 86400 * 7)

            if new_amount == 0:
                continue

            # get the current value just in case

            current_amount = r.get('{0}:vote:{1}:{2}'.format(
                fleet_id, char_id, ship_class))
            current_amount = current_amount.decode('utf-8')

            if not current_amount:
                current_amount = 0
            current_amount = int(current_amount)

            # next, we want to either increment or decrement

            if new_amount < 0:
                # decrement?
                # cap the amount a user can peel off the overall vote to be no more
                # than what they are already contributing

                new_amount = min(current_amount, abs(new_amount))

                if new_amount == 0:
                    # do nothing
                    continue

                # peel off the amount from what the user has, and the overall vote
                r.decr('{0}:vote:{1}'.format(fleet_id, ship_class), new_amount)
                r.decr(
                    '{0}:vote:{1}:{2}'.format(fleet_id, char_id, ship_class),
                    new_amount)

            else:
                # increment
                r.incr('{0}:vote:{1}'.format(fleet_id, ship_class), new_amount)
                r.incr(
                    '{0}:vote:{1}:{2}'.format(fleet_id, char_id, ship_class),
                    new_amount)

        # barf, paplink
        _logger.securitylog(__name__,
                            'voted fleet composition',
                            detail=detailstring,
                            ipaddress=ipaddress,
                            charid=char_id)

        js = json.dumps({})
        return Response(js, status=200, mimetype='application/json')
Ejemplo n.º 13
0
def ts3_validate_users(ts3conn):

    import ldap
    import common.ldaphelpers as _ldaphelpers
    import common.credentials.ldap as _ldap
    import common.credentials.ts3 as _ts3
    import common.logger as _logger
    import time
    import ts3

    from tri_core.common.tsgroups import teamspeak_groups


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


    # generic purge of TS from non-blue ldap users
    # only matches people who should not have a TS identity

    dn = 'ou=People,dc=triumvirate,dc=rocks'
    filterstr = '(&(!(accountStatus=blue))(teamspeakuid=*))'
    attributes = ['characterName', 'uid', 'esiAccessToken' ]
    code, result = _ldaphelpers.ldap_search(__name__, dn, filterstr, attributes)

    if code == False:
        return

    if result == None:
        # nobody. no problem.
        pass
    else:
        result_count = len(result)
        # some ppl are banned/whatever and have a TS identity!
        msg = '{0} unauthorized users with a TS identity'.format(result_count)
        _logger.log('[' + __name__ + '] {}'.format(msg),_logger.LogLevel.WARNING)

        for user in result.keys():

            charid = int( result[user]['uid'] )

            # decouple their TS identities from their LDAP entry

            mod_attrs = []
            mod_attrs.append((ldap.MOD_DELETE, 'teamspeakdbid', None ))
            mod_attrs.append((ldap.MOD_DELETE, 'teamspeakuid', None ))
            try:
                ldap_conn.modify_s(user, mod_attrs)
                msg = 'purged TS identity from unauthorized user: {}'.format(user)
                _logger.log('[' + __name__ + '] {}'.format(msg),_logger.LogLevel.INFO)
            except ldap.LDAPError as error:
                _logger.log('[' + __name__ + '] unable to purge TS entries for {0}: {1}'.format(dn, error),_logger.LogLevel.ERROR)

    # validate ts3 online users

    # skip these users
    skip = [ 'ServerQuery Guest', 'sovereign' ]

    # it turns out clientdblist() are just users who are online, rather than ALL users or something
    # this does not audit users-within-groups apparently
    try:
        resp = ts3conn.clientdblist()
    except ts3.query.TS3QueryError as err:
        _logger.log('[' + __name__ + '] ts3 error: {0}'.format(err),_logger.LogLevel.ERROR)
        return

    clients = []

    for user in resp.parsed:
        serviceuser = user['client_nickname']
        if serviceuser in skip:
            # we don't want to waste time on internal users
            continue
        clients.append(int(user['cldbid']))

    # get the rest of the clients out of the various groups, then de-dupe

    try:
        resp = ts3conn.servergrouplist()
        groups = resp.parsed
    except ts3.query.TS3QueryError as err:
        _logger.log('[' + __name__ + '] ts3 error: {0}'.format(err),_logger.LogLevel.ERROR)
        return

    for group in groups:
        # dig out all the users in each group
        groupid = int(group['sgid'])

        skip = [ 8 ]

        if groupid in skip:
            # TS does NOT like looking into default groups (?)
            continue
        try:
            resp = ts3conn.servergroupclientlist(sgid=group['sgid'])
            result = resp.parsed
        except ts3.query.TS3QueryError as err:
            _logger.log('[' + __name__ + '] ts3 error: {0}'.format(err),_logger.LogLevel.ERROR)
            return
        for client in result:
            clients.append(int(client['cldbid']))

    # deduplicate using set and filter out dbids that have to be skipped
    # server query only, basically

    skip = [ 1 ]

    clients = set(clients) - set(skip)
    clients = list(clients)

    clientcount = len(clients)
    _logger.log('[' + __name__ + '] distinct ts3 client identities: {0}'.format(clientcount),_logger.LogLevel.DEBUG)

    # get the current TS3 client list
    try:
        resp = ts3conn.clientlist()
        live_clients = resp.parsed
    except ts3.query.TS3QueryError as err:
        _logger.log('[' + __name__ + '] unable to fetch TS client list: {0}'.format(err),_logger.LogLevel.WARNING)


    # start validating clients, online or otherwise
    for ts_dbid in clients:

        # this should never fail since the dbid we have is fed from the ts3 client list upstream

        try:
            resp = ts3conn.clientdbinfo(cldbid=ts_dbid)
            user = resp.parsed
        except ts3.query.TS3QueryError as err:
            _logger.log('[' + __name__ + '] ts3 (uid: {0}) error: "{1}"'.format(ts_dbid, err),_logger.LogLevel.WARNING)
            return

        user_nick = user[0]['client_nickname']
        _logger.log('[' + __name__ + '] Validating ts3 user "{0}"'.format(user_nick),_logger.LogLevel.DEBUG)

        user_lastip = user[0]['client_lastip']
        user_lastconn = int(user[0]['client_lastconnected'])
        user_conns = int(user[0]['client_totalconnections'])
        user_created = int(user[0]['client_created'])

        token = None # for token checks later
        orphan = False # for detached TS registrations
        kicked = False # users can be kicked in a few spots prior to final purge spot

        # we explicitly check only for blue users that have this dbid.
        # this means people with non-blue status (public, banned) lose their TS

        dn = 'ou=People,dc=triumvirate,dc=rocks'
        filterstr='(&(accountStatus=blue)(teamspeakdbid={}))'.format(ts_dbid)
        attrlist=['characterName', 'uid', 'esiAccessToken' ]

        code, result = _ldaphelpers.ldap_search(__name__, dn, filterstr, attributes)

        if code == False:
            return

        if result == None:
            # this can happen if the TS user has an entry in the TS db but nothing in ldap
            # this user will be dealt with downstream
            _logger.log('[' + __name__ + '] ts3 orphan dbid: {0}'.format(ts_dbid),_logger.LogLevel.INFO)
            registered_username = None
            orphan = True


        elif len(result) == 1:
            # the dbid is matched to an single ldap user
            (dn, info), = result.items()
            charname = info['characterName']
            charid = int( info['uid'] )
            registered_username = charname

            try:
                token = info['esiAccessToken']
            except Exception as e:
                token = None

            _logger.log('[' + __name__ + '] charid {0} validated non-orphan'.format(charid),_logger.LogLevel.DEBUG)

            # do a TS group validate

            code, result = teamspeak_groups(charid)

            if code == False:
                msg = 'unable to setup teamspeak groups for {0}: {1}'.format(charname, result)
                _logger.log('[' + __name__ + '] {}'.format(msg),_logger.LogLevel.ERROR)
                continue

        elif len(result) > 1:
            # multiple TS registrations. naughty.

            _logger.log('[' + __name__ + '] multiple unique TS identities attached to accounts: {0}'.format(result),_logger.LogLevel.WARNING)

            orphan = True
            # boot from TS...
            reason = 'Please re-register your TS on CORE. On only one character.'
            try:
                resp = ts3conn.clientkick(reasonid=5, reasonmsg=reason, clid=clid)
                _logger.log('[' + __name__ + '] ts3 user {0} kicked from server: active orphan'.format(user_nick),_logger.LogLevel.WARNING)
                _logger.log('[' + __name__ + '] TS db: "{0}", client: "{1}"'.format(registered_username,client_username),_logger.LogLevel.DEBUG)
                kicked = True
                _logger.securitylog(__name__, 'ts3 duplicate character', charname=registered_username, date=user_lastconn, ipaddress=user_lastip)
            except ts3.query.TS3QueryError as err:
                _logger.log('[' + __name__ + '] ts3 error: "{0}"'.format(err),_logger.LogLevel.ERROR)

            # ...break the ldap <--> TS link on each character
            for user in result.keys():

                charid = int( result[user]['uid'] )

                mod_attrs = []
                mod_attrs.append((ldap.MOD_DELETE, 'teamspeakdbid', None ))
                mod_attrs.append((ldap.MOD_DELETE, 'teamspeakuid', None ))
                try:
                    ldap_conn.modify_s(user, mod_attrs)
                    msg = 'purged TS identity from duplicate user: {}'.format(user)
                    _logger.log('[' + __name__ + '] {}'.format(msg),_logger.LogLevel.INFO)
                except ldap.LDAPError as error:
                    _logger.log('[' + __name__ + '] unable to purge TS entries for {0}: {1}'.format(dn, error),_logger.LogLevel.ERROR)


        # if the user is online, we want to make sure that the username matches
        # what is in the teamspeak table

        for client in live_clients:
            clid = client['clid']
            cldbid = int(client['client_database_id'])
            client_username = client['client_nickname']


            if orphan is True and cldbid == ts_dbid:
                # a registered TS user needs to have an ESI token on their LDAP
                reason = 'You are no longer registered on CORE'
                continue
                try:
                    resp = ts3conn.clientkick(reasonid=5, reasonmsg=reason, clid=clid)
                    _logger.log('[' + __name__ + '] ts3 user {0} kicked from server: no active registration'.format(user_nick),_logger.LogLevel.WARNING)
                    _logger.log('[' + __name__ + '] TS db: "{0}", client: "{1}"'.format(registered_username,client_username),_logger.LogLevel.DEBUG)
                    kicked = True
                    _logger.securitylog(__name__, 'ts3 without ESI', charname=registered_username, date=user_lastconn, ipaddress=user_lastip)
                except ts3.query.TS3QueryError as err:
                    _logger.log('[' + __name__ + '] ts3 error: "{0}"'.format(err),_logger.LogLevel.ERROR)

            if not client_username or not registered_username:
                continue

            if client_username.lower() == registered_username.lower() and token == None and kicked == False:
                # a registered TS user needs to have an ESI token on their LDAP
                reason = 'Please login to CORE. Your token has become invalid.'
                try:
                    resp = ts3conn.clientkick(reasonid=5, reasonmsg=reason, clid=clid)
                    _logger.log('[' + __name__ + '] ts3 user {0} kicked from server: no esi token'.format(user_nick),_logger.LogLevel.WARNING)
                    _logger.log('[' + __name__ + '] TS db: "{0}", client: "{1}"'.format(registered_username,client_username),_logger.LogLevel.DEBUG)
                    kicked = True
                    _logger.securitylog(__name__, 'ts3 without ESI', charname=registered_username, date=user_lastconn, ipaddress=user_lastip)
                except ts3.query.TS3QueryError as err:
                    _logger.log('[' + __name__ + '] ts3 error: "{0}"'.format(err),_logger.LogLevel.ERROR)

            if cldbid == ts_dbid and client_username != registered_username and kicked == False:
                # online user has a username that does not match records.
                # "encourage" fixing this.
                reason = 'Please use your main character name as your teamspeak nickname, without any tags'
                try:
                    resp = ts3conn.clientkick(reasonid=5, reasonmsg=reason, clid=clid)
                    _logger.log('[' + __name__ + '] ts3 user {0} kicked from server: mismatch'.format(user_nick),_logger.LogLevel.WARNING)
                    _logger.log('[' + __name__ + '] TS db: "{0}", client: "{1}"'.format(registered_username,client_username),_logger.LogLevel.DEBUG)
                    _logger.securitylog(__name__, 'ts3 name mismatch', charname=registered_username, date=user_lastconn, ipaddress=user_lastip, detail='wrong name: {0}'.format(client_username))
                    kicked = True
                except ts3.query.TS3QueryError as err:
                    _logger.log('[' + __name__ + '] ts3 error: "{0}"'.format(err),_logger.LogLevel.ERROR)

        # handle orphan TS users

        if orphan == True:
            # log the shit out of the orphan user

            lastconnected = time.gmtime(user_lastconn)
            lastconnected_iso = time.strftime("%Y-%m-%dT%H:%M:%S", lastconnected)
            created = time.gmtime(user_created)
            created_iso = time.strftime("%Y-%m-%dT%H:%M:%S", created)
            _logger.log('[' + __name__ + '] Orphan ts3 user: {0}'.format(user_nick), _logger.LogLevel.WARNING)
            _logger.log('[' + __name__ + '] User {0} created: {1}, last login: {2}, last ip: {3}, total connections: {4}'.format(
                user_nick,created_iso,lastconnected_iso,user_lastip,user_conns),
                _logger.LogLevel.WARNING
            )


            # first kick from the server if they are on, asking them to re-register
            # to do that i need the client id, which is not the client db id, because
            # of f*****g course it isn't

            for client in live_clients:
                clid = client['clid']
                cldbid = client['client_database_id']
                if cldbid == ts_dbid and kicked == False:
                    try:
                        reason = 'You are detached from CORE. Please configure services.'
                        resp = ts3conn.clientkick(reasonid=5, reasonmsg=reason, clid=clid)
                        _logger.log('[' + __name__ + '] ts3 user {0} kicked from server (orphan user)'.format(user_nick),_logger.LogLevel.WARNING)
                        _logger.securitylog(__name__, 'orphan ts3 user', charname=user_nick, date=user_lastconn, ipaddress=user_lastip)
                    except ts3.query.TS3QueryError as err:
                        _logger.log('[' + __name__ + '] ts3 error: "{0}"'.format(err),_logger.LogLevel.WARNING)

            # now remove the client from the ts3 database

            try:
                resp = ts3conn.clientdbdelete(cldbid=ts_dbid)
                _logger.log('[' + __name__ + '] ts3 user {0} removed'.format(user_nick),_logger.LogLevel.WARNING)
            except ts3.query.TS3QueryError as err:
                _logger.log('[' + __name__ + '] ts3 error: "{0}"'.format(err),_logger.LogLevel.WARNING)

            # client removed. gg.
        else:
            # nothing to do. yer good.
            pass
Ejemplo n.º 14
0
def ts3_logs(ts3conn):

    import common.credentials.ts3 as _ts3
    import common.logger as _logger
    import redis
    import ts3
    import time
    import re

    # parse ts3 server logs and dump useful things into security log

    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))

    # is there another instance running?

    try:
        if r.get('ts_log_processing') is not None:
            msg = 'existing TS log processing running. skipping.'
            _logger.log('[' + __name__ + '] {0}'.format(msg), _logger.LogLevel.WARNING)
            return
        else:
            r.setex('ts_log_processing', 3600, 'yep')

    except Exception as err:
        _logger.log('[' + __name__ + '] Redis error: ' + str(err), _logger.LogLevel.ERROR)
        return

    # we start from a zero position and work our way back to a checkpoint in redis

    try:
        checkpoint = r.get('ts_log_checkpoint')
        checkpoint = float(checkpoint.decode('utf-8'))
    except Exception as err:
        _logger.log('[' + __name__ + '] Redis error: ' + str(err), _logger.LogLevel.ERROR)

    position = 1
    stop = False
    logcount = 0

    while position > 0 and stop == False:
        # a small position offset so i can start/stop cleanly
        if position == 1:
            position = 0
        try:
            resp = ts3conn.logview(lines=100, reverse=1, begin_pos=position)
        except ts3.query.TS3QueryError as err:
            _logger.log('[' + __name__ + '] ts3 error: {0}'.format(err),_logger.LogLevel.ERROR)
        for line in resp.parsed:
            if 'last_pos' in line.keys():
                file_size = line['file_size']
                last_pos = line['last_pos']
                position = int(last_pos)
                _logger.log('[' + __name__ + '] ts3 log size: {0}, last position: {1}'.format(file_size, last_pos),_logger.LogLevel.DEBUG)
            if 'l' in line.keys():
                logcount += 1
                entry = line['l']
                # parse the log

                try:
                    date, level, target, server, logline = entry.split('|', 4)
                except ValueError as err:
                    _logger.log('[' + __name__ + '] ts3 log line unparsable: {0}'.format(logline),_logger.LogLevel.ERROR)
                    continue
                # convert the date into epoch

                # example date: 2017-05-13 14:34:58.223587
                # or 0000002017-07-21 19:50:58.

                # why are you doing this shit, ts3?
                date = date.replace('000000', '')
                date = re.sub('\.$', '.000000', date) # make sure %f has something to use
                try:
                    date = time.strptime(date, "%Y-%m-%d %H:%M:%S.%f")
                except ValueError as e:
                    _logger.log('[' + __name__ + '] ts3 log data pollution: {0}'.format(e),_logger.LogLevel.ERROR)
                    continue

                date = time.mktime(date)

                # stop processing logs as this is where we last were
                if date <= checkpoint:
                    stop = True
                    position = 0
                    break

                # regex out login events

                # example line:
                # client connected 'Sightless 1'(id:1615) from 135.23.200.132:50743

                loginmatch = re.match( r"^client connected \'(.*)\'\(id:(.*)\) from (.*):(.*)$", logline, re.M)
                if loginmatch:
                    charname = loginmatch.group(1)
                    clientid = loginmatch.group(2)
                    ip = loginmatch.group(3)

                    # dump the result into the security log
                    _logger.securitylog(__name__, 'ts3 login', charname=charname, ipaddress=ip, date=date)


    _logger.log('[' + __name__ + '] new ts3 log entries: {0}'.format(logcount),_logger.LogLevel.INFO)

    # set the redis checkpoint time for the next run

    checkpoint = time.time()
    try:
        r.set('ts_log_checkpoint', checkpoint)
        r.delete('ts_log_processing')
    except Exception as err:
        _logger.log('[' + __name__ + '] Redis error: ' + str(err), _logger.LogLevel.ERROR)
Ejemplo n.º 15
0
def audit_forums():

    import common.logger as _logger
    import common.credentials.database as _database
    import common.credentials.forums as _forumcreds
    import common.ldaphelpers as _ldaphelpers
    import common.request_esi
    from tri_core.common.testing import vg_blues, vg_alliances
    from common.graphite import sendmetric
    from collections import defaultdict
    import json
    import urllib
    import html
    import MySQLdb as mysql
    import re

    import hashlib
    from passlib.hash import ldap_salted_sha1
    import uuid

    _logger.log('[' + __name__ + '] auditing forums',_logger.LogLevel.INFO)

    try:
        sql_conn_core = 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

    try:
        sql_conn_forum = mysql.connect(
            database=_forumcreds.mysql_db,
            user=_forumcreds.mysql_user,
            password=_forumcreds.mysql_pass,
            host=_forumcreds.mysql_host)
    except mysql.Error as err:
        _logger.log('[' + __name__ + '] mysql error: ' + str(err), _logger.LogLevel.ERROR)
        return False

    # get everything from the permissions table

    cursor = sql_conn_core.cursor()
    query = 'SELECT allianceID,forum FROM Permissions'
    forum_mappings = dict()

    try:
        cursor.execute(query)
        rows = cursor.fetchall()
    except mysql.Error as err:
        _logger.log('[' + __name__ + '] mysql error: ' + str(err), _logger.LogLevel.ERROR)
        return False
    finally:
        cursor.close()
        sql_conn_core.close()

    # this maps the alliance id to the primary forum group

    for row in rows:
        forum_mappings[row[0]] = row[1]

    # handle vanguard blues as well
    # these are a special case of limited access

    for alliance in vg_blues():
        forum_mappings[alliance] = 73

    # get all the forum users and stuff them into a dict for later processing

    cursor = sql_conn_forum.cursor()
    query = 'SELECT name, member_group_id, mgroup_others, ip_address, pp_main_photo, pp_thumb_photo FROM core_members'
    try:
        cursor.execute(query)
        rows = cursor.fetchall()
    except mysql.Error as err:
        _logger.log('[' + __name__ + '] mysql forum error: ' + str(err), _logger.LogLevel.ERROR)
        return False

    total_users = len(rows)
    _logger.log('[' + __name__ + '] forum users: {0}'.format(total_users), _logger.LogLevel.INFO)
    sendmetric(__name__, 'forums', 'statistics', 'total_users', total_users)

    users = dict()
    orphan = 0

    # special forum users that are not to be audited
    special = [ 'Admin', 'Sovereign' ]

    for charname, primary_group, secondary_string, last_ip,  pp_main_photo, pp_thumb_photo in rows:

        # dont work on these
        if charname in special:
            continue

        if charname == '' or charname == None:
            continue

        # recover user information
        users[charname] = dict()
        users[charname]['charname'] = charname
        users[charname]['doomheim'] = False
        users[charname]['primary'] = primary_group
        users[charname]['last_ip'] = last_ip
        users[charname]['pp_main_photo'] = pp_main_photo
        users[charname]['pp_thumb_photo'] = pp_thumb_photo

        # convert the comma separated list of groups to an array of integers

        secondaries = []
        for item in secondary_string.split(','):
            if not item == '' and not item == ' ':
                secondaries.append(int(item))
        users[charname]['secondary'] = secondaries

        cn, _ = _ldaphelpers.ldap_normalize_charname(charname)

        # match up against ldap data
        dn = 'ou=People,dc=triumvirate,dc=rocks'
        filterstr='cn={0}'.format(cn)
        attributes = ['authGroup', 'accountStatus', 'uid', 'alliance', 'corporation' ]
        code, result = _ldaphelpers.ldap_search(__name__, dn, filterstr, attributes)
        if code == False:
            _logger.log('[' + __name__ + '] ldap error: {0}'.format(result), _logger.LogLevel.ERROR)
            return

        if result == None:
            # nothing in ldap.
            # you WILL be dealt with later!
            orphan += 1
            users[charname]['charid'] = None
            users[charname]['alliance'] = None
            users[charname]['authgroups'] = []
            users[charname]['accountstatus'] = None
            users[charname]['corporation'] = None
        else:
            (dn, info), = result.items()

            alliance = info.get('alliance')
            corporation = info.get('corporation')

            if alliance is not None:
                alliance = int(alliance)
            if corporation is not None:
                corporation = int(corporation)

            users[charname]['alliance'] = alliance
            users[charname]['corporation'] = corporation

            users[charname]['charid'] = int( info['uid'] )
            users[charname]['accountstatus'] = info['accountStatus']
            users[charname]['authgroups'] = info['authGroup']

    # postprocessing

    _logger.log('[' + __name__ + '] forum users with no LDAP entry: {0}'.format(orphan),_logger.LogLevel.INFO)

    # map each user to a charID given that the username is exactly
    # a character name

    really_orphan = 0

    for charname in users:
        user = users[charname]
        primary = user['primary']

        if user['charid'] == None:
            # need to map the forum username to a character id
            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')
            _logger.log('[' + __name__ + '] /search output: {}'.format(result), _logger.LogLevel.DEBUG)

            if not code == 200:
                _logger.log('[' + __name__ + '] error searching for user {0}: {1}'.format(charname, result['error']),_logger.LogLevel.INFO)
                continue
            if len(result) == 0:
                really_orphan += 1
                users[charname]['doomheim'] = True
            if len(result) == 1:

                # make a stub ldap entry for them

                charid = result['character'][0]
                users[charname]['charid'] = charid

                # the forum stub exists only to pin
                _ldaphelpers.ldap_create_stub(charname, charid, authgroups=['public', 'forum_stub'])

    _logger.log('[' + __name__ + '] forum users who have biomassed: {0}'.format(really_orphan),_logger.LogLevel.INFO)

    # at this point every forum user has been mapped to their character id either via ldap or esi /search
    # work through each user and determine if they are correctly setup on the forums

    non_tri = 0
    for charname in users:

        charid = users[charname]['charid']

        if users[charname]['doomheim'] == True:
            forumpurge(charname)
            continue

        authgroups = users[charname]['authgroups']
        alliance = users[charname]['alliance']
        corporation = users[charname]['corporation']
        primary_group = users[charname]['primary']
        secondary_groups = users[charname]['secondary']
        forum_lastip = users[charname]['last_ip']

        if users[charname]['pp_main_photo'] is None:
        # set forum portrait to their in-game avatar if the user doesn't have one

            esi_url = 'characters/{0}/portrait/'.format(charid)
            code, result = common.request_esi.esi(__name__, esi_url, 'get')
            _logger.log('[' + __name__ + '] /characters portrait output: {}'.format(result), _logger.LogLevel.DEBUG)

            if code == 200:

                portrait = result['px128x128'].replace("http", "https")
                query = 'UPDATE core_members SET pp_main_photo=%s, pp_thumb_photo=%s, pp_photo_type="custom" WHERE name = %s'
                try:
                    cursor.execute(query, (portrait, portrait, charname,))
                    sql_conn_forum.commit()
                except Exception as err:
                    _logger.log('[' + __name__ + '] mysql error: ' + str(err), _logger.LogLevel.ERROR)
                    return

            else:
                # something broke severely
                _logger.log('[' + __name__ + '] /characters portrait API error {0}: {1}'.format(code, result['error']), _logger.LogLevel.ERROR)


        ## start doing checks

        # only people in tri get anything other than public access on the tri forums
        # forum public/unprivileged groupid: 2

        vanguard = forum_mappings.keys()

        if alliance not in vanguard and primary_group != 2:

            # this char is not in a vanguard alliance or blue but has non-public forum access
            forumpurge(charname)

            # log

            _logger.securitylog(__name__, 'forum demotion', charid=charid, charname=charname, ipaddress=forum_lastip)
            msg = 'non-vanguard character with privileged access demoted. charname: {0} ({1}), alliance: {2}, primary group: {3}, secondary group(s): {4}, last ip: {5}'.format(
                charname, charid, alliance, primary_group, secondary_groups, forum_lastip
            )
            _logger.log('[' + __name__ + '] {}'.format(msg),_logger.LogLevel.WARNING)
            non_tri += 1

        if alliance in vanguard:

            correct_primary = forum_mappings[alliance]
            correct_secondaries = []
            # construct the list of correct secondary groups

            for authgroup in authgroups:
                mapping = authgroup_map(authgroup)
                if not mapping == None:
                    correct_secondaries.append(mapping)

            # map any custom corp groups
            corpgroup = corp_map(corporation)
            if not corpgroup == None:
                correct_secondaries.append(corpgroup)

            ## deal with secondary groups

            # remove secondaries
            msg = 'char {0} current secondaries: {1} correct secondaries: {2}'.format(charname, secondary_groups, correct_secondaries)
            _logger.log('[' + __name__ + '] {}'.format(msg),_logger.LogLevel.DEBUG)

            secondaries_to_remove = list( set(secondary_groups) - set(correct_secondaries) )

            # ips forum likes a comma separated list of secondaries
            if len(secondaries_to_remove) > 0:
                change = ''
                for group in correct_secondaries:
                    change = change + str(group) + ','
                change = change[:-1] # peel off trailing comma

                query = 'UPDATE core_members SET mgroup_others=%s WHERE name=%s'
                try:
                    cursor.execute(query, (change, charname,))
                    sql_conn_forum.commit()
                    msg = 'removed secondary group(s) {0} from {1}'.format(secondaries_to_remove, charname)
                    _logger.log('[' + __name__ + '] {}'.format(msg),_logger.LogLevel.INFO)
                except Exception as err:
                    _logger.log('[' + __name__ + '] mysql error: ' + str(err), _logger.LogLevel.ERROR)
                    return False

            secondaries_to_add = list( set(correct_secondaries) - set(secondary_groups) )

            # ips forum likes a comma separated list of secondaries
            if len(secondaries_to_add) > 0:
                change = ''
                for group in correct_secondaries:
                    change = change + str(group) + ','
                change = change[:-1] # peel off trailing comma

                query = 'UPDATE core_members SET mgroup_others=%s WHERE name=%s'
                try:
                    cursor.execute(query, (change, charname,))
                    sql_conn_forum.commit()
                    msg = 'added secondary group(s) {0} to {1}'.format(secondaries_to_add, charname)
                    _logger.log('[' + __name__ + '] {}'.format(msg),_logger.LogLevel.INFO)
                except Exception as err:
                    _logger.log('[' + __name__ + '] mysql error: ' + str(err), _logger.LogLevel.ERROR)
                    return False

            if not correct_primary == primary_group:
                query = 'UPDATE core_members SET member_group_id=%s WHERE name=%s'
                try:
                    cursor.execute(query, (correct_primary, charname,))
                    sql_conn_forum.commit()
                    _logger.log('[' + __name__ + '] adjusted primary forum group of {0} to {1}'.format(charname, correct_primary),_logger.LogLevel.INFO)
                except Exception as err:
                    _logger.log('[' + __name__ + '] mysql error: ' + str(err), _logger.LogLevel.ERROR)
                    return False
    cursor.close()
    sql_conn_forum.close()
    _logger.log('[' + __name__ + '] non-vanguard forum users reset: {0}'.format(non_tri),_logger.LogLevel.INFO)
Ejemplo n.º 16
0
def core_corpaudit(charid):


    # do a corp level audit of who has services

    ipaddress = request.headers['X-Real-Ip']
    log_charid = request.args.get('log_charid')

    logger = getlogger('core.corpaudit')
    securitylog('corp audit information request', ipaddress=ipaddress, charid=log_charid)

    try:
        charid = int(charid)
    except ValueError:
        msg = 'charid parameters must be integer: {0}'.format(charid)
        logger.warning(msg)
        js = json.dumps({ 'error': msg})
        resp = Response(js, status=401, mimetype='application/json')
        return resp

    corporation_id_list = []
    character_id_list = []

    dn = 'ou=People,dc=triumvirate,dc=rocks'

    code, char_result = _ldaphelpers.ldap_search(__name__, dn, '(&(|(uid={0})(altOf={0}))(esiAccessToken=*))'
                                                 .format(charid), ['uid', 'corporation', 'corporationName', 'characterName'])

    if code == False:
        error = 'failed to fetch characters for {0}: ({1}) {2}'.format(charid, code, char_result)
        logger.error(error)
        js = json.dumps({'error': error})
        resp = Response(js, status=500, mimetype='application/json')
        return resp

    for cn in char_result:
        data = char_result[cn]
        allowed_roles = ['Director', 'Personnel_Manager']
        roles = check_role(data['uid'], allowed_roles)

        if not roles:
            msg = "character {0} has insufficient roles for {1}".format(data['characterName'], data['corporationName'])
            logger.info(msg)
            continue

        msg = 'character {0} has sufficient roles to view corp auditing information'.format(cn)
        logger.debug(msg)

        if data['corporation'] not in corporation_id_list:
            request_url = 'corporations/{0}/members/'.format(data['corporation'])
            code, result = common.request_esi.esi(__name__, request_url, method='get', charid=data['uid'], version='v3')

            if not code == 200:
                # something broke severely
                msg = 'corporations API error {0}: {1}'.format(code, result)
                logger.error(msg)
                result = {'code': code, 'error': msg}
                return code, result
            character_id_list = result
            corporation_id_list.append(data['corporation'])

    # start constructing which member has what
    users = dict()
    with ThreadPoolExecutor(25) as executor:
        futures = { executor.submit(fetch_chardetails, user): user for user in character_id_list }
        for future in as_completed(futures):
            #charid = futures[future]['character_id']
            data = future.result()

            if data is not None:
                users[data['charname']] = data
    js = json.dumps(users)
    resp = Response(js, status=200, mimetype='application/json')
    return resp
Ejemplo n.º 17
0
def core_audit_alliance(allianceid):

    # logging

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

    charid = request.args.get('charid')

    if charid is None:
        error = 'need a charid to authenticate with'
        js = json.dumps({'error': error})
        resp = Response(js, status=405, mimetype='application/json')
        return resp

    _logger.securitylog(__name__,
                        'alliance audit',
                        ipaddress=ipaddress,
                        charid=charid,
                        detail='alliance {0}'.format(allianceid))

    # ALL is a meta-endpoint for all the vanguard alliances, plus viral

    viral = 99003916
    alliances = [allianceid]

    if allianceid == 'ALL':
        alliances = vg_alliances()
        alliances.append(viral)
    # check for auth groups

    allowed_roles = ['tsadmin', 'command']
    dn = 'ou=People,dc=triumvirate,dc=rocks'

    code, result = _ldaphelpers.ldap_search(__name__, dn,
                                            '(uid={})'.format(charid),
                                            ['authGroup'])

    if code == 'error':
        error = 'unable to check auth groups roles for {0}: ({1}) {2}'.format(
            charid, code, result)
        _logger.log('[' + __name__ + ']' + error, _logger.LogLevel.ERROR)
        js = json.dumps({'error': error})
        resp = Response(js, status=500, mimetype='application/json')
        return resp

    if result == None:
        msg = 'charid {0} not in ldap'.format(charid)
        _logger.log('[' + __name__ + '] {}'.format(msg),
                    _logger.LogLevel.ERROR)
        js = json.dumps({'error': msg})
        return Response(js, status=404, mimetype='application/json')

    (_, result), = result.items()

    if not "command" in result['authGroup']:
        error = 'insufficient corporate roles to access this endpoint.'
        _logger.log('[' + __name__ + '] ' + error, _logger.LogLevel.INFO)
        js = json.dumps({'error': error})
        resp = Response(js, status=403, mimetype='application/json')
        return resp

    alliancedata = defaultdict(list)

    # process each alliance with an individual process

    with ProcessPoolExecutor(15) as executor:
        futures = {
            executor.submit(alliance_data, charid, alliance): allianceid
            for alliance in alliances
        }
        for future in as_completed(futures):
            data = future.result()
            alliance_id = data['alliance_id']
            alliance_name = data['alliance_name']

            alliancedata[alliance_id] = dict()
            alliancedata[alliance_id]['name'] = alliance_name
            alliancedata[alliance_id]['corp_data'] = data['alliance_corps']

    js = json.dumps(alliancedata)
    resp = Response(js, status=200, mimetype='application/json')

    return resp
Ejemplo n.º 18
0
def core_password(charid):

    import json
    import ldap
    import ldap.modlist
    import common.logger as _logger
    import common.credentials.ldap as _ldap
    import common.ldaphelpers as _ldaphelpers
    import uuid
    import hashlib
    from flask import Response, request
    from passlib.hash import ldap_salted_sha1

    # reset password of charid

    ipaddress = request.headers['X-Real-Ip']
    log_charid = request.args.get('log_charid')  # logging purposes

    _logger.securitylog(__name__,
                        'reset password',
                        detail='target charid {0}'.format(charid),
                        ipaddress=ipaddress,
                        charid=log_charid)

    # initialize connections

    try:
        ldap_conn = ldap.initialize(_ldap.ldap_host, bytes_mode=False)
        ldap_conn.simple_bind_s(_ldap.admin_dn, _ldap.admin_dn_password)
    except ldap.LDAPError as error:
        msg = 'LDAP connection error: {}'.format(error)
        _logger.log('[' + __name__ + '] {}'.format(msg),
                    _logger.LogLevel.ERROR)
        js = json.dumps({'error': msg})
        return Response(js, status=500, mimetype='application/json')

    # find the user. partly to get the dn, partly to validate.

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

    if code == False:
        msg = 'unable to fetch ldap information: {}'.format(error)
        _logger.log('[' + __name__ + '] {}'.format(msg),
                    _logger.LogLevel.ERROR)
        js = json.dumps({'error': msg})
        return Response(js, status=500, mimetype='application/json')

    if result == None:
        msg = 'charid {0} not in ldap'.format(charid)
        _logger.log('[' + __name__ + '] {}'.format(msg),
                    _logger.LogLevel.ERROR)
        js = json.dumps({'error': msg})
        return Response(js, status=404, mimetype='application/json')

    (dn, info), = result.items()

    # new password

    password = uuid.uuid4().hex[:8]
    password_hash = ldap_salted_sha1.hash(password)
    password_hash = password_hash.encode('utf-8')

    mod_attrs = [(ldap.MOD_REPLACE, 'userPassword', [password_hash])]

    try:
        ldap_conn.modify_s(dn, mod_attrs)
    except ldap.LDAPError as error:
        msg = 'unable to modify password of {0}: {1}'.format(dn, error)
        _logger.log('[' + __name__ + '] {}'.format(msg),
                    _logger.LogLevel.ERROR)
        js = json.dumps({'error': msg})
        return Response(js, status=500, mimetype='application/json')

    response = {'password': password}
    return Response(json.dumps(response),
                    status=200,
                    mimetype='application/json')
Ejemplo n.º 19
0
def core_trisupers():

    # logging

    logger = getlogger('core.trisupers')

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

    charid = request.args.get('charid')

    if charid is None:
        error = 'need a charid to authenticate with'
        js = json.dumps({'error': error})
        resp = Response(js, status=405, mimetype='application/json')
        return resp

    securitylog('supers audit', ipaddress=ipaddress, charid=charid)

    # check for auth groups

    allowed_roles = ['tsadmin']
    dn = 'ou=People,dc=triumvirate,dc=rocks'

    code, result = _ldaphelpers.ldap_search(__name__, dn,
                                            '(uid={})'.format(charid),
                                            ['authGroup'])

    if code == 'error':
        error = 'unable to check auth groups roles for {0}: ({1}) {2}'.format(
            charid, code, result)
        logger.error(error)
        js = json.dumps({'error': error})
        resp = Response(js, status=500, mimetype='application/json')
        return resp

    if result == None:
        msg = 'charid {0} not in ldap'.format(charid)
        logger.error(msg)
        js = json.dumps({'error': msg})
        return Response(js, status=404, mimetype='application/json')

    (_, result), = result.items()

    if not "command" in result['authGroup']:
        error = 'insufficient authgroup privileges to access this endpoint.'
        logger.error(error)
        js = json.dumps({'error': error})
        resp = Response(js, status=403, mimetype='application/json')
        return resp

    # get people in trisupers

    pilots = []

    filterstr = '(&(esiAccessToken=*)(alliance=933731581)(authGroup=trisupers))'
    attrlist = ['uid', 'corporation', 'characterName', 'altOf']

    code, result = _ldaphelpers.ldap_search(__name__, dn, filterstr, attrlist)

    for _, info in result.items():
        pilots.append(info['uid'])

    result_supers = result

    # iterate through and add each charid from registered alts

    for charid in pilots:
        filterstr = 'altOf={}'.format(charid)
        attrlist = ['uid', 'corporation', 'characterName', 'altOf']

        code, result = _ldaphelpers.ldap_search(__name__, dn, filterstr,
                                                attrlist)
        if result:
            result_supers = {**result_supers, **result}

    supers = dict()

    with ThreadPoolExecutor(40) as executor:
        futures = {
            executor.submit(audit_pilot, result_supers[cn]): cn
            for cn in result_supers
        }
        for future in as_completed(futures):
            data = future.result()

            try:
                supers.update(data)
            except Exception as err:
                msg = 'super audit for failed: {0}'.format(err)
                logger.error(msg)

    supers_cleaned = {}

    for key in supers:
        supers_cleaned[supers[key]['item_id']] = supers[key]

    js = json.dumps(supers_cleaned)
    return Response(js, status=200, mimetype='application/json')
Ejemplo n.º 20
0
def core_corpcapitals():
    dn = 'ou=People,dc=triumvirate,dc=rocks'
    # logging

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

    charid = request.args.get('charid')

    if charid is None:
        error = 'need a charid to authenticate with'
        js = json.dumps({'error': error})
        resp = Response(js, status=405, mimetype='application/json')
        return resp

    securitylog('capitals audit', ipaddress=ipaddress, charid=charid)

    # check for auth groups

    allowed_roles = ['Director', 'Personnel_Manager']
    roles = check_role(charid, allowed_roles)

    if not roles:
        return Response(json.dumps({'error': "forbidden"}),
                        status=403,
                        mimetype='application/json')

    # get corp

    code, result = _ldaphelpers.ldap_search(__name__, dn,
                                            '(uid={})'.format(charid),
                                            ['corporation'])

    if code == 'error':
        error = 'unable to check auth groups roles for {0}: ({1}) {2}'.format(
            charid, code, result)
        logger.error(error)
        js = json.dumps({'error': error})
        resp = Response(js, status=500, mimetype='application/json')
        return resp

    if result == None:
        msg = 'charid {0} not in ldap'.format(charid)
        logger.error(msg)
        js = json.dumps({'error': msg})
        return Response(js, status=404, mimetype='application/json')

    (_, result), = result.items()

    # get super pilots

    filterstr = '(&(esiAccessToken=*)(corporation={0}))'.format(
        result['corporation'])
    attrlist = ['uid', 'corporation', 'characterName', 'altOf']

    code_capitals, result_capitals = _ldaphelpers.ldap_search(
        __name__, dn, filterstr, attrlist)

    supers = dict()

    with ThreadPoolExecutor(75) as executor:
        futures = {
            executor.submit(audit_pilot_capitals, result_capitals[cn]): cn
            for cn in result_capitals
        }
        for future in as_completed(futures):
            data = future.result()

            try:
                supers.update(data)
            except Exception as err:
                msg = 'capital audit for failed: {0}'.format(err)
                logger.error(msg)

    supers_cleaned = {}

    for key in supers:
        supers_cleaned[supers[key]['item_id']] = supers[key]

    js = json.dumps(supers_cleaned)
    return Response(js, status=200, mimetype='application/json')
Ejemplo n.º 21
0
def core_blacklist_pending():

    # get all users that are waiting to be blacklisted

    logger = logging.getLogger('tri_api.endpoints.blacklist.pending')

    ipaddress = request.args.get('log_ip')
    log_charid = request.args.get('charid')
    cookie = request.cookies.get('tri_core')

    securitylog('viewed blacklist',
                detail='pending',
                ipaddress=ipaddress,
                charid=log_charid)

    dn = 'ou=People,dc=triumvirate,dc=rocks'
    filterstr = 'authGroup=ban_pending'
    attrlist = [
        'characterName', 'uid', 'altOf', 'banReason', 'banReportedBy',
        'banDescription', 'banDate'
    ]
    code, result = _ldaphelpers.ldap_search(__name__, dn, filterstr, attrlist)

    if code == False:
        msg = 'unable to fetch ldap information: {}'.format(error)
        logger.error(msg)
        js = json.dumps({'error': msg})
        return Response(js, status=500, mimetype='application/json')

    # start converting the bans into friendly information

    banlist = dict()

    if result is None:
        # nothing pending
        return Response('{}', status=200, mimetype='application/json')

    for dn, info in result.items():
        charname = info['characterName']
        reporter_charid = info['banReportedBy']

        banlist[charname] = info
        # map the times to friendlier info

        if info['banDate']:
            banlist[charname]['banDate'] = time.strftime(
                '%Y-%m-%d', time.localtime(info['banDate']))

        # map the main of the banned alt to a name

        if info['altOf'] is not None:

            main_charid = info['altOf']

            banlist[charname]['isalt'] = True
            banlist[charname]['main_charid'] = main_charid

            main_name = _ldaphelpers.ldap_uid2name(main_charid)

            if main_name is False or None:

                request_url = 'characters/{0}/'.format(main_charid)
                code, result = common.request_esi.esi(__name__, request_url,
                                                      'get')

                if not code == 200:
                    msg = '/characters API error {0}: {1}'.format(code, result)
                    logger.warning(msg)
                    banlist[charname]['main_charname'] = 'Unknown'
                else:
                    banlist[charname]['main_charname'] = result['name']
            else:
                banlist[charname]['main_charname'] = main_name
        else:
            banlist[charname]['main_charid'] = None
            banlist[charname]['main_charname'] = None
            banlist[charname]['isalt'] = False

        # map the reporter id to name

        reporter_name = _ldaphelpers.ldap_uid2name(reporter_charid)

        if not reporter_name and reporter_charid:
            request_url = 'characters/{0}/'.format(info['banReportedBy'])
            code, result = common.request_esi.esi(__name__, request_url, 'get')

            if not code == 200:
                msg = '/characters API error {0}: {1}'.format(code, result)
                logger.warning(msg)
                banlist[charname]['banReportedBy'] = 'Unknown'
            else:
                banlist[charname]['banReportedBy'] = result['name']
        else:
            banlist[charname]['banReportedBy'] = reporter_name

    return Response(json.dumps(banlist),
                    status=200,
                    mimetype='application/json')
Ejemplo n.º 22
0
def command_mining_ledger():

    # process mining ledger as a command level view

    log_address = request.args.get('log_ip')
    log_charid = request.args.get('log_charid')
    msg = 'command mining ledger view'

    _logger.securitylog(__name__,
                        msg,
                        ipaddress=log_address,
                        charid=log_charid)

    # fetch the common moon pricing data

    _, moon_data = moon_typedata()

    # the following alliances are taxed on moon miner ownership

    taxed_alliances = vg_renters() + [933731581]

    # process all the alliances and each corp within them

    command_ledger = dict()
    ledger_characters = list()

    for alliance in taxed_alliances:

        request_url = 'alliances/{0}/'.format(alliance)
        code, result = common.request_esi.esi(__name__,
                                              request_url,
                                              'get',
                                              version='v3')

        alliancename = result.get('name')

        request_url = 'alliances/{0}/corporations/'.format(alliance)
        code, result = common.request_esi.esi(__name__,
                                              request_url,
                                              'get',
                                              version='v1')

        if not code == 200:
            # something broke severely
            _logger.log(
                '[' + __name__ +
                '] /alliances/{0}/corporations API error {1}: {2}'.format(
                    alliance, code, result), _logger.LogLevel.ERROR)
            resp = Response(result, status=code, mimetype='application/json')
            return resp

        msg = '/alliances/{0}/corporations output: {1}'.format(
            alliance, result)
        _logger.log(
            '[' + __name__ + '] /mining/observers output: '.format(result),
            _logger.LogLevel.DEBUG)

        for corporation in result:
            # fetch a character per-corp that can fetch the mining ledger data

            corporation = int(corporation)

            # get the corp name

            request_url = 'corporations/{0}/'.format(corporation)
            code, result = common.request_esi.esi(__name__,
                                                  request_url,
                                                  method='get',
                                                  version='v4')
            if not code == 200:
                corpname = 'Unknown'
            else:
                corpname = result.get('name')

            command_ledger[corporation] = {
                'corpid': corporation,
                'corpname': corpname,
                'alliancename': alliancename,
                'moons': 0,
                'this_month': 0,
                'last_month': 0,
                'this_month_total': 0,
                'last_month_total': 0,
                'token': False,
            }

            mining_scope = 'esi-industry.read_corporation_mining.v1'

            dn = 'ou=People,dc=triumvirate,dc=rocks'
            filterstr = '(&(corporation={0})(esiScope={1})(corporationRole=Director)(esiAccessToken=*))'.format(
                corporation, mining_scope)
            attrlist = ['uid']
            code, result = _ldaphelpers.ldap_search(__name__, dn, filterstr,
                                                    attrlist)

            if code == False:
                msg = 'unable to fetch ldap information: {}'.format(error)
                _logger.log('[' + __name__ + '] {}'.format(msg),
                            _logger.LogLevel.ERROR)
                resp = Response(msg, status=500, mimetype='application/json')
                return resp

            if result is None:
                # no compatible token, for whatever reason. no ledger.
                command_ledger[corporation]['token'] = False
                continue
            else:
                # has a token
                command_ledger[corporation]['token'] = True

            # grab the first compatible token. it literally doesn't matter which.
            # dictionaries are explicitly unordered though!

            user = next(iter(result))
            charid = result[user]['uid']
            msg = 'using {0} ({1}) for checking corp {2}'.format(
                user, charid, corporation)

            # TODO: REMOVE DEBUG
            _logger.log('[' + __name__ + '] {}'.format(msg),
                        _logger.LogLevel.INFO)

            ledger_characters.append(charid)

    with ThreadPoolExecutor(15) as executor:
        futures = {
            executor.submit(mining_ledger, moon_data, charid): charid
            for charid in ledger_characters
        }
        for future in as_completed(futures):
            code, result = future.result()

            if code is not True:
                continue

            for structure_id in result:

                data = result[structure_id]

                if data.get('ledger') is None:
                    print(data)
                    continue

                # fetch the actual ledger data

                last_month = data['ledger']['last_month']['taxable']
                this_month = data['ledger']['this_month']['taxable']
                corpid = int(data['corpid'])
                corpname = data['corpname']

                # there ought to already be ledger data. add to it.

                command_ledger[corpid]['moons'] += 1
                command_ledger[corpid]['this_month'] += this_month
                command_ledger[corpid]['last_month'] += last_month
                command_ledger[corpid]['this_month_total'] += data['ledger'][
                    'this_month']['total']
                command_ledger[corpid]['last_month_total'] += data['ledger'][
                    'last_month']['total']
    print(command_ledger[1975749457])

    js = json.dumps(command_ledger)
    return Response(js, status=200, mimetype='application/json')
Ejemplo n.º 23
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
Ejemplo n.º 24
0
def core_blacklist():

    # get all users that are confirmed blacklisted

    logger = logging.getLogger('tri_api.endpoints.blacklist.confirmed')

    ipaddress = request.args.get('log_ip')
    log_charid = request.args.get('charid')

    securitylog('viewed blacklist',
                detail='confirmed',
                ipaddress=ipaddress,
                charid=log_charid)

    dn = 'ou=People,dc=triumvirate,dc=rocks'
    filterstr = 'accountStatus=banned'
    attrlist = [
        'characterName', 'uid', 'altOf', 'banApprovedBy', 'banApprovedOn',
        'banReason', 'banReportedBy', 'banDescription', 'allianceName'
    ]
    code, result = _ldaphelpers.ldap_search(__name__, dn, filterstr, attrlist)

    if code == False:
        msg = 'unable to fetch ldap information: {}'.format(error)
        logger.error(msg)
        js = json.dumps({'error': msg})
        return Response(js, status=500, mimetype='application/json')

    # start converting the bans into friendly information

    banlist = dict()

    for dn, info in result.items():
        charname = info['characterName']
        approver_charid = info['banApprovedBy']
        reporter_charid = info['banReportedBy']

        banlist[charname] = info
        # map the times to friendlier info

        if info['banApprovedOn']:
            banlist[charname]['banApprovedOn'] = time.strftime(
                '%Y-%m-%d', time.localtime(info['banApprovedOn']))

        # how the f**k did ban reasons/descriptions get stored this way?!

        banlist[charname]['banReason'] = ban_convert(info['banReason'])

        # map the main of the banned alt to a name

        if info['altOf'] is not None:

            main_charid = info['altOf']

            banlist[charname]['isalt'] = True
            banlist[charname]['main_charid'] = main_charid

            main_name = _ldaphelpers.ldap_uid2name(main_charid)

            if main_name is False or None:

                request_url = 'characters/{0}/'.format(main_charid)
                code, result = common.request_esi.esi(__name__, request_url,
                                                      'get')

                if not code == 200:
                    msg = '/characters/{0}/ API error {1}: {2}'.format(
                        charid, code, result)
                    logger.warning(msg)
                    banlist[charname]['main_charname'] = 'Unknown'
                else:
                    banlist[charname]['main_charname'] = result['name']
            else:
                banlist[charname]['main_charname'] = main_name
        else:
            banlist[charname]['isalt'] = False
            banlist[charname]['main_charname'] = None
            banlist[charname]['main_charid'] = None

        # map the reporter and approver's ids to names

        approver_name = _ldaphelpers.ldap_uid2name(approver_charid)
        reporter_name = _ldaphelpers.ldap_uid2name(reporter_charid)

        if not approver_name and approver_charid:
            # no longer in ldap?
            request_url = 'characters/{0}/'.format(approver_charid)
            code, result = common.request_esi.esi(__name__, request_url, 'get')
            if not code == 200:
                msg = '/characters/{0}/ API error {1}: {2}'.format(
                    approver_charid, code, result)
                logger.warning(msg)
                banlist[charname]['banApprovedBy'] = 'Unknown'
            else:
                banlist[charname]['banApprovedBy'] = result['name']
        else:
            banlist[charname]['banApprovedBy'] = approver_name

        if not reporter_name and reporter_charid:
            request_url = 'characters/{0}/'.format(info['banReportedBy'])
            code, result = common.request_esi.esi(__name__, request_url, 'get')
            if not code == 200:
                msg = '/characters/{0}/ API error {1}: {2}'.format(
                    reporter_charid, code, result)
                logger.warning(msg)
                banlist[charname]['banReportedBy'] = 'Unknown'
            else:
                banlist[charname]['banReportedBy'] = result['name']
        else:
            banlist[charname]['banReportedBy'] = reporter_name

    return Response(json.dumps(banlist),
                    status=200,
                    mimetype='application/json')
Ejemplo n.º 25
0
def auth_discord_callback():

    from requests_oauthlib import OAuth2Session
    import common.logger as _logger
    import common.credentials.discord as _discord
    import tri_core.common.storetokens as _storetokens
    import common.ldaphelpers as _ldaphelpers
    import common.request_esi

    from tri_core.common.session import readsession

    # constants

    client_id = _discord.client_id
    client_secret = _discord.client_secret
    redirect_url = _discord.redirect_url
    base_url = _discord.base_url

    token_url = base_url + '/oauth2/token'
    base_auth_url = base_url + '/oauth2/authorize'

    # fetch the tri core session so we can tie this to a charid
    # we're re-verifying!

    cookie = request.cookies.get('tri_core')

    if cookie == None:
        msg = 'You need to be logged into tri core with your main in order to register on discord'
        msg += '<br>'
        msg += 'Try logging into CORE again<br>'
        return make_response(msg)

    payload = readsession(cookie)

    if payload == False:
        msg = 'There was a problem reading your tri core cookie.'
        msg += '<br>'
        msg += 'Try logging into CORE again<br>'
        return make_response(msg)

    charid = payload['charID']

    # security logging

    ipaddress = request.headers['X-Real-Ip']
    _logger.log(
        '[' + __name__ +
        '] discord registration for charid {0} received'.format(charid),
        _logger.LogLevel.INFO)
    _logger.securitylog(__name__,
                        'discord registration initiated',
                        ipaddress=ipaddress,
                        charid=charid)

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

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

    try:
        result = oauth_session.fetch_token(
            token_url,
            client_secret=client_secret,
            authorization_response=request.url,
        )
    except Exception as error:
        _logger.log(
            '[' + __name__ +
            '] unable to fetch discord access token: {0}'.format(error),
            _logger.LogLevel.ERROR)
        return ('ERROR: ' + str(error))

    atoken = result['access_token']
    rtoken = result['refresh_token']
    expires = result['expires_at']

    # store the discord token

    _storetokens.storetokens(charid,
                             atoken,
                             rtoken,
                             expires,
                             token_type='discord')

    _logger.log('[' + __name__ + '] discord registration complete',
                _logger.LogLevel.INFO)
    discord_widget = '<iframe src="https://discordapp.com/widget?id=358117641724100609&theme=dark" width="350" height="500" allowtransparency="true" frameborder="0"></iframe>'
    message = "You are done.<br> Connect to discord if you have not already<br>"
    message += discord_widget
    return message
Ejemplo n.º 26
0
def core_blacklist_confirm(ban_charid):

    # promote someone from 'ban pending' to 'banned'

    # logging
    logger = logging.getLogger('tri_api.endpoints.blacklist.confirm')

    ipaddress = request.args.get('log_ip')
    log_charid = request.args.get('charid')

    if log_charid is None:
        log_charid = 90622096

    securitylog('blacklist confirm'.format(ban_charid),
                ipaddress=ipaddress,
                charid=log_charid,
                detail='charid {0}'.format(ban_charid))

    # fetch details including main + alts

    dn = 'ou=People,dc=triumvirate,dc=rocks'
    filterstr = '(|(altOf={0})(uid={0}))'.format(ban_charid)
    attrlist = ['characterName', 'uid', 'altOf', 'authGroup', 'accountStatus']
    code, result = _ldaphelpers.ldap_search(__name__, dn, filterstr, attrlist)

    if code == False:
        msg = 'unable to fetch ldap information: {}'.format(error)
        logger.error(msg)
        js = json.dumps({'error': msg})
        return Response(js, status=500, mimetype='application/json')

    if result == None:
        msg = 'no such charid {0}'.format(ban_charid)
        logger.warning(msg)
        js = json.dumps({'error': msg})
        return Response(js, status=404, mimetype='application/json')

    for dn, info in result.items():

        # change account status and authgroup to banned
        # add an approved by/time

        # do sanity checks

        status = info['accountStatus']
        charname = info['characterName']
        altof = info['altOf']
        groups = info['authGroup']

        if status == 'banned':
            msg = 'user {0} already banned'.format(charname)
            logger.info(msg)
            return Response({}, status=200, mimetype='application/json')

        if 'ban_pending' not in groups and altof is None:
            # need the ban pending auth group
            msg = 'user {0} not pending ban confirmation'.format(charname)
            logger.info(msg)
            return Response({}, status=200, mimetype='application/json')
        else:
            msg = 'setting dn {0} to banned'.format(dn)
            logger.info(msg)

        # moving from pending to 'actually banned'
        _ldaphelpers.purge_authgroups(dn, ['ban_pending'])
        _ldaphelpers.update_singlevalue(dn, 'authGroup', 'banned')

        # actually ban
        _ldaphelpers.update_singlevalue(dn, 'accountStatus', 'banned')
        _ldaphelpers.update_singlevalue(dn, 'banApprovedBy', log_charid)
        _ldaphelpers.update_singlevalue(dn, 'banApprovedOn', time.time())

    # all done
    return Response({}, status=200, mimetype='application/json')
Ejemplo n.º 27
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')
Ejemplo n.º 28
0
def core_blacklist_add():

    # put someone into the blacklist, pending confirmation

    # logging
    logger = logging.getLogger('tri_api.endpoints.blacklist.add')
    ipaddress = request.args.get('log_ip')
    log_charid = request.args.get('charid')

    # optional charid override for command line tinkering to record correctly

    # parse form data
    ban_data = dict()
    ban_data['banreason'] = request.form.get('reason')
    ban_data['bandescription'] = request.form.get('description')
    ban_data['main'] = request.form.get('charName')

    ban_data['alts'] = list()
    altfields = ['alt1', 'alt2', 'alt3', 'alt4', 'alt5', 'alt6', 'alt7']

    for alt in altfields:
        altname = request.form.get(alt)
        if altname is not None and altname != '':
            ban_data['alts'].append(altname)

    main_charname = ban_data['main']

    securitylog('blacklist add',
                ipaddress=ipaddress,
                charid=log_charid,
                detail='charname {0}'.format(main_charname))

    # batch search the main and alts

    chardata = dict()

    for charname in [main_charname] + ban_data['alts']:
        query = {
            'categories': 'character',
            'language': 'en-us',
            'search': charname,
            'strict': 'true'
        }
        query = urllib.parse.urlencode(query)
        request_url = 'search/?' + query
        code, result = common.request_esi.esi(__name__,
                                              request_url,
                                              'get',
                                              version='v2')

        # will allow a hardfail for bans

        if result.get('character') is None:
            msg = 'unable to identify charname: {0}'.format(charname)
            logger.error(msg)
            js = json.dumps({'error': msg})
            return Response(js, status=400, mimetype='application/json')
        if len(result.get('character')) > 1:
            msg = 'more than one return for charname {0} from ESI search'.format(
                main_charname)
            logger.warning(msg)

        # even on a single result its still a list

        for charid in result.get('character'):
            chardata[charname] = charid

    # ban the main

    main_charid = chardata[main_charname]
    result = ban_character(main_charid)

    if not result:
        msg = 'unable to ban character {0}'.format(main_charname)
        logger.error(msg)
        js = json.dumps({'error': msg})
        return Response(js, status=400, mimetype='application/json')

    # tag the ban with flavor text

    cn, dn = _ldaphelpers.ldap_normalize_charname(main_charname)

    _ldaphelpers.add_value(dn, 'banReason', ban_data['banreason'])
    _ldaphelpers.add_value(dn, 'banDescription', ban_data['bandescription'])
    _ldaphelpers.add_value(dn, 'banReportedBy', log_charid)
    _ldaphelpers.add_value(dn, 'banDate', time.time())

    # try to find registered alts and ban those too

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

    if code == False:
        msg = 'unable to fetch ldap information: {}'.format(error)
        logger.error(msg)
        js = json.dumps({'error': msg})
        return Response(js, status=500, mimetype='application/json')

    error = False

    if result:
        for dn, info in result.items():
            charid = info['uid']
            charname = info['characterName']
            ban_result = ban_character(main_charid)
            if not result:
                msg = 'unable to ban character {0}'.format(charname)
                js = json.dumps({'error': msg})
                logger.error(msg)
                error = True

    # work through the submitted alts and ban those too

    if len(ban_data['alts']) == 0:
        # were done here.
        if not error:
            return Response({}, status=200, mimetype='application/json')
        else:
            return Response(js, status=400, mimetype='application/json')

    msg = 'banning {0} {1} alts'.format(len(ban_data['alts']), main_charname)
    logger.info(msg)

    for alt_charname in ban_data['alts']:
        alt_charid = chardata[alt_charname]
        ban_result = ban_character(alt_charid, altof=main_charid)

        if not ban_result:
            msg = 'unable to ban {0} alt {1}'.format(main_charname,
                                                     alt_charname)
            logger.error(msg)
            js = json.dumps({'error': msg})
            error = True
            # keep trying tho

    if not error:
        return Response({}, status=200, mimetype='application/json')
    else:
        return Response(js, status=400, mimetype='application/json')
Ejemplo n.º 29
0
def maint_jabber_logs():
    import common.logger as _logger
    import common.ldaphelpers as _ldaphelpers

    import re
    import time

    # process ejabberd log files and store relevant information in the security log
    # this setup assumes the log file will be copied to a spot that does
    # not mind being truncated on a regular basis

    # the regexs will need a minor rewrite for ipv6
    accepted = r"^(\S+ \S+) .* Accepted authentication for (\S+) by ejabberd_auth_ldap from ::FFFF:(.*)$"
    failed = r"^(\S+ \S+) .* Failed authentication for (\S+) from ::FFFF:(.*)$"

    logfile = '/var/log/ejabberd/logparser/ejabberd.log'
    try:
        file = open(logfile, 'r+')
    except Exception as err:
        # the file to process is obviously not there.
        _logger.log(
            '[' + __name__ +
            '] unable to open ejabberd logfile: {}'.format(err),
            _logger.LogLevel.ERROR)
        return

    _logger.log('[' + __name__ + '] processing ejabberd logfile',
                _logger.LogLevel.INFO)

    for line in file.readlines():
        match_accept = re.match(accepted, line)
        match_failed = re.match(failed, line)
        action = 'jabber login'

        if match_accept:
            date = match_accept.group(1)
            cn = match_accept.group(2)
            ip_address = match_accept.group(3)
            detail = 'successful'
        elif match_failed:
            date = match_failed.group(1)
            ip_address = match_failed.group(3)
            detail = 'failed'

            # convert the attempted login user to maybe a valid dn
            login_user = match_failed.group(2)
            user_match = re.match('^(\S+)@triumvirate.rocks', login_user)
            if user_match:
                cn = user_match.group(1)
            else:
                # can't match it to a dn so it'll be a mystery
                cn = None
        else:
            continue

        # convert a date format like the following to epoch
        # 2017-07-31 06:03:41.918
        try:
            date = time.strptime(date, "%Y-%m-%d %H:%M:%S.%f")
        except ValueError as e:
            _logger.log(
                '[' + __name__ +
                '] ejabberd log data pollution: {0}'.format(e),
                _logger.LogLevel.ERROR)
            continue

        date = time.mktime(date)

        # (try to) fetch the charid from ldap

        result = _ldaphelpers.ldap_cn2id(__name__, cn)

        try:
            uid = result['uid']
        except Exception as e:
            uid = None

        # store into the security log

        _logger.securitylog(__name__,
                            action,
                            charid=uid,
                            ipaddress=ip_address,
                            date=date,
                            detail=detail)

    # wipe the file and close out
    file.seek(0)
    file.truncate()
    file.close()
    _logger.log('[' + __name__ + '] finished processing ejabberd log',
                _logger.LogLevel.INFO)