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