def test_char(charid): import common.request_esi import common.logger as _logger # get character affiliations affilliations = _esihelpers.esi_affiliations(charid) allianceid = affilliations.get('allianceid') charname = affilliations.get('charname') corpid = affilliations.get('corpid') if not allianceid: # no alliance no blue result = {'code': 0} return 200, result # test the character's alliance code, result = test_alliance(allianceid) return code, result
def ban_character(charid, altof=None): # take a charid and ban it (technially, set it to pending approval) # flavor text added in other logic logger = logging.getLogger('tri_api.endpoints.blacklist.add.character') dn = 'ou=People,dc=triumvirate,dc=rocks' filterstr = 'uid={}'.format(charid) attrlist = ['characterName'] code, result = _ldaphelpers.ldap_search(__name__, dn, filterstr, attrlist) if code == False: msg = 'unable to fetch ldap information: {}'.format(error) logger.error(msg) return False # if a stub is created, i would have to search again affiliations = _esihelpers.esi_affiliations(charid) charname = affiliations['charname'] cn, dn = _ldaphelpers.ldap_normalize_charname(charname) if result == None: # not currently in ldap, create a stub _ldaphelpers.ldap_create_stub(charid=charid, authgroups=['public', 'ban_pending'], altof=altof) else: _ldaphelpers.add_value(dn, 'authGroup', 'ban_pending') msg = 'banning {0}'.format(charname) logger.info(msg) return True
def makesession(charid): import common.logger as _logger import common.request_esi import common.credentials.core as _core import common.esihelpers as _esihelpers import common.database as _database import base64 import urllib.parse import phpserialize import MySQLdb as mysql import json import uuid import os import hmac import hashlib import time import redis from Crypto import Random from Crypto.Cipher import AES key = _core.key # construct the user's session. this is how you auth to core. # we're mimicing laravel structure exactly. it is finikiy. payload = dict() # actually useful data payload['charID'] = charid payload['csrf_token'] = uuid.uuid4().hex payload['_previous'] = dict() payload['_previous']['url'] = 'https://auth.triumvirate.rocks/eve/callback' payload['ip_address'] = '' payload['user_agent'] = '' # i have literally no idea what purpose this serves. payload['_flash'] = dict() payload['_flash']['new'] = '' payload['_flash']['old'] = '' # see: vendor/symfony/http-foundation/Session/Storage/MetadataBag.php # for why the _sf2_meta shit is here # c: create, u: updated, l: lifetime payload['_sf2_meta'] = dict() payload['_sf2_meta']['u'] = time.time() payload['_sf2_meta']['l'] = 0 payload['_sf2_meta']['c'] = time.time() # first, get the user data. affiliations = _esihelpers.esi_affiliations(charid) charname = affiliations.get('charname') payload['charName'] = charname payload['corpID'] = affiliations.get('corpid') payload['corpName'] = affiliations.get('corpname') payload['allianceID'] = affiliations.get('allianceid') payload['allianceName'] = affiliations.get('alliancename') # the scope controls whether you are tri or a tri blue # this in turn controls various access levels # this is legacy code for laravel - python services determine access in different ways. if payload['allianceID'] == 933731581: # "tri alliance only" scope payload['scope'] = 2 else: payload['scope'] = 1 payload = phpserialize.dumps(payload) payload = base64.b64encode(payload) # AES requires inputs to be multiples of 16 # # laravel seems exceedingly picky with the session id size and padding # so we're going to mimic it exactly sessionid = uuid.uuid4().hex + uuid.uuid4().hex sessionid = sessionid[:40] # feed the session data into the session table # mysql is just to make laravel happy try: sql_conn = mysql.connect(database=_database.DB_DATABASE, user=_database.DB_USERNAME, password=_database.DB_PASSWORD, host=_database.DB_HOST) except mysql.Error as err: _logger.log('[' + __name__ + '] mysql error: ' + str(err), _logger.LogLevel.ERROR) return False cursor = sql_conn.cursor() now = time.time() # make sure this is the only session. i would adjust table schema but that'd probably # break laravel # purge null sessions too try: query = 'DELETE FROM sessions WHERE charID = %s' cursor.execute( query, (charid, ), ) except mysql.Error as err: _logger.log('[' + __name__ + '] mysql error: ' + str(err), _logger.LogLevel.ERROR) return False try: query = 'INSERT INTO sessions (id, charID, charName, payload, last_activity) VALUES(%s, %s, %s, %s, %s)' cursor.execute( query, ( sessionid, charid, charname, payload, now, ), ) except mysql.Error as err: _logger.log('[' + __name__ + '] mysql error: ' + str(err), _logger.LogLevel.ERROR) return False finally: cursor.close() sql_conn.commit() sql_conn.close() # store the session id + payload into redis r = redis.StrictRedis(host='localhost', port=6379, db=0) try: r.client_list() except redis.exceptions.ConnectionError as err: _logger.log('[' + __name__ + '] Redis connection error: ' + str(err), _logger.LogLevel.ERROR) except redis.exceptions.ConnectionRefusedError as err: _logger.log('[' + __name__ + '] Redis connection error: ' + str(err), _logger.LogLevel.ERROR) except Exception as err: logger.error('[' + __name__ + '] Redis generic error: ' + str(err)) try: # store the session in redis and set it to timeout in a week r.set(sessionid, payload) expires = 86400 * 7 # 1 week r.expire(sessionid, expires) except Exception as err: _logger.log('[' + __name__ + '] Redis error: ' + str(err), _logger.LogLevel.ERROR) # construct the encrypted cookie contents, which consists of three things: # the initialization vector for AES, a sha256 hash, and the AES encrypted session id key = base64.b64decode(key) iv = uuid.uuid4( ).hex[:16] # need a 16 digit initial value for the encryption iv = iv.encode('utf-8') # structure the php serialized thing in a way that laravel likes # don't rock the boat sessionid = 's:40:"' + str( sessionid ) + '";\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10' sessionid = sessionid.encode('utf-8') _logger.log( '[' + __name__ + '] key length: {0}, iv length: {1}, sessionid length: {2}'.format( len(key), len(iv), len(sessionid)), _logger.LogLevel.DEBUG) # the madness as demanded by laravel # see: vendor/laravel/framework/src/Illuminate/Encryption/Encrypter.php try: value = AES.new(key, AES.MODE_CBC, iv).encrypt(sessionid) except Exception as error: # how this can happen with fixed lengths i'm unclear... _logger.log('[' + __name__ + '] AES error: ' + str(error), _logger.LogLevel.ERROR) return False value = base64.b64encode(value) iv = base64.b64encode(iv) hash = hmac.new(key, msg=iv + value, digestmod=hashlib.sha256) cookie = dict() cookie['mac'] = hash.hexdigest() cookie['iv'] = iv.decode('utf-8') cookie['value'] = value.decode('utf-8') cookie = json.dumps(cookie) cookie = base64.b64encode(cookie.encode('utf-8')) cookie = cookie.decode('utf-8') return cookie
def fetch_chardetails(charid): chardetails = dict() logger = getlogger('core.corpaudit.chardetails.{0}'.format(charid)) dn = 'ou=People,dc=triumvirate,dc=rocks' filterstr='(uid={})'.format(charid) attrlist=['characterName', 'authGroup', 'teamspeakdbid', 'esiAccessToken', 'altOf', 'corporation', 'lastKill', 'lastKillTime','corporationName'] code, result = _ldaphelpers.ldap_search(__name__, dn, filterstr, attrlist) if result is None or not code: # no result? simple response. chardetails['location'] = 'Unknown' chardetails['corporation'] = 'Unknown' chardetails['online'] = 'Unknown' chardetails['last_online'] = 'Unknown' chardetails['token_status'] = False chardetails['teamspeak_status'] = False chardetails['isalt'] = 'Unknown' chardetails['lastKill'] = None chardetails['lastKillTime'] = None chardetails['altof'] = None # fetch affiliations affiliations = _esihelpers.esi_affiliations(charid) chardetails['corporation'] = affiliations.get('corpname') chardetails['charname'] = affiliations.get('charname') else: try: (dn, info), = result.items() except ValueError: print("error: {}".format(charid)) chardetails['charname'] = info['characterName'] # convert last kill time into human readable try: chardetails['lastKill'] = info['lastKill'] except Exception as e: chardetails['lastKill'] = None try: killtime = int(info['lastKillTime']) killtime = time.strftime("%Y/%m/%d, %H:%M:%S", time.localtime(killtime)) chardetails['lastKillTime'] = killtime except Exception as e: chardetails['lastKillTime'] = None corp_id = info['corporation'] if corp_id is None: print("error: {} no corp".format(charid)) chardetails['corporation'] = info['corporationName'] # does the char have a token? try: detail = info['esiAccessToken'] if len(detail) > 0: chardetails['token_status'] = True else: chardetails['token_status'] = False except Exception as e: chardetails['token_status'] = False # teamspeak registration? try: detail = info['teamspeakdbid'] if len(detail) > 0: chardetails['teamspeak_status'] = True else: chardetails['teamspeak_status'] = False except Exception as e: chardetails['teamspeak_status'] = False # is this an alt? # cast the altof detail to something useful try: detail = info['altOf'] except Exception as e: detail = None # str(None) == False if str(detail).isdigit(): chardetails['isalt'] = True request_url = 'characters/{0}/'.format(detail) code, result = common.request_esi.esi(__name__, request_url, 'get') if not code == 200: msg = '/characters/{0}/ API error {1}: {2}'.format(detail, code, result) logger.warning(msg) try: chardetails['altof'] = result['name'] except KeyError as error: msg = 'User does not exist: {0})'.format(charid) logger.error(msg) chardetails['altof'] = 'Unknown' else: chardetails['altof'] = None chardetails['isalt'] = False ## start fetching character-specific information # request_url = 'characters/{0}/location/'.format(charid) code, result = common.request_esi.esi(__name__, request_url, method='get', charid=charid, version='v1') if not code == 200: # it doesn't really matter msg = 'characters loction API error {0}: {1}'.format(code, result) logger.debug(msg) location = None chardetails['location_id'] = location chardetails['location'] = 'Unknown' else: # can include either station_id or structure_id location = result['solar_system_id'] chardetails['location_id'] = location request_url = 'characters/{0}/location/'.format(charid) code, result = common.request_esi.esi(__name__, request_url, method='get', charid=charid, version='v1') if not code == 200: # it doesn't really matter msg = 'characters loction API error {0}: {1}'.format(code, result) logger.debug(msg) location = None else: # can include either station_id or structure_id location = result['solar_system_id'] chardetails['location_id'] = location # map the location to a name if location == None: chardetails['location'] = 'Unknown' else: request_url = 'universe/systems/{0}/'.format(location) code, result = common.request_esi.esi(__name__, request_url, 'get', version='v4') if not code == 200: msg = '{0} API error: {1}'.format(request_url, result) logger.error(msg) chardetails['location'] = 'Unknown' else: chardetails['location'] = result['name'] # get online status request_url = 'characters/{0}/online/'.format(charid) code, result = common.request_esi.esi(__name__, request_url, method='get', charid=charid, version='v2') if not code == 200: # it doesn't really matter msg = '{0} API error: {1}'.format(request_url, result) logger.debug(msg) location = None chardetails['online'] = 'Unknown' chardetails['last_online'] = 'Unknown' else: chardetails['online'] = result['online'] chardetails['last_online'] = result['last_login'] try: request_url_corp = 'corporations/{0}/'.format(corp_id) code_corp, result_corp = common.request_esi.esi(__name__, request_url_corp, 'get') if not code_corp == 200: _logger.log('[' + __name__ + '] /corporations API error {0}: {1}'.format(code_corp, result_corp['error']), _logger.LogLevel.WARNING) msg = '{0} API error: {1}'.format(request_url_corp, result) logger.warning(msg) else: chardetails['corporation'] = result_corp['name'] except KeyError as error: msg = 'corporation id does not exist: {0}'.format(corp_id) logger.error(msg) charname = None return chardetails
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 registeruser(charid, atoken, rtoken, isalt=False, altof=None, tempblue=False, renter=False): # put the barest skeleton of information into ldap/mysql logger = getlogger('core.sso.registeruser') # get character affiliations headers = { 'Content-Type': 'application/json', 'Accept': 'application/json' } if isalt: msg = 'registering user {0} (alt of {1})'.format(charid, altof) else: msg = 'registering user {}'.format(charid) logger.info(msg) # affiliations affiliations = _esihelpers.esi_affiliations(charid) charname = affiliations.get('charname') corpid = affiliations.get('corpid') corpname = affiliations.get('corporation_name') allianceid = affiliations.get('allianceid') alliancename = affiliations.get('alliancename') # sort out basic auth groups if tempblue: # default level of access for vanguard blues authgroups = ['public', 'vanguardBlue'] accountstatus = 'blue' if renter: # renter groups authgroups = ['public', 'renters'] accountstatus = 'public' else: # default level of access authgroups = ['public'] accountstatus = 'public' if allianceid == 933731581: # tri specific authgroup authgroups.append('triumvirate') accountstatus = 'blue' # setup the service user/pass cn, dn = _ldaphelpers.ldap_normalize_charname(charname) dn = "cn={},ou=People,dc=triumvirate,dc=rocks".format(cn) # create the stub result, code = _ldaphelpers.ldap_create_stub(charid=charid, charname=charname, isalt=isalt, altof=altof, accountstatus=accountstatus, authgroups=authgroups, rtoken=rtoken, atoken=atoken) # if result: # # if isalt: # msg = 'new user {0} (alt of {1} registered'.format(charname, altof) # else: # msg = 'new user {0} registered'.format(charname) return
def user_audit(dn, details): if dn == 'ou=People,dc=triumvirate,dc=rocks': return logger = logging.getLogger('audit.core.user') msg = 'auditing user: {0}'.format(dn) logger.debug(msg) # groups that a non-blue user is allowed to have safegroups = set([ 'public', 'ban_pending', 'banned', ]) if details['uid'] == None: logger.error('dn {0} has no charid'.format(dn)) return False charid = int(details['uid']) status = details['accountStatus'] altof = details['altOf'] charname = details['characterName'] raw_groups = details['authGroup'] esi_rtoken = details['esiRefreshToken'] discord_rtoken = details['discordRefreshToken'] ldap_discorduid = details['discorduid'] ldap_discord2fa = details['discord2fa'] ldap_discordname = details['discordName'] ldap_scopes = details['esiScope'] ldap_roles = details['corporationRole'] if ldap_scopes is None: ldap_scopes = [] if ldap_roles is None: ldap_roles = [] if details['lastLogin'] is not None: ldap_lastlogin = float(details['lastLogin']) else: ldap_lastlogin = 0 # we do not need to keep entries on people where there's no meaningful data purged = purge(dn, details) if purged: # this has been purged, we're done here. msg = 'successfully purged dn {0}'.format(dn) logger.debug(msg) return ## bugfix sanity checks # there was once a bug where people would be marked as alts of themselves # this results in interesting effects if altof == charid: _ldaphelpers.update_singlevalue(dn, 'altOf', None) # there was once a time where tokens were purged but their associated scopes and roles were not if esi_rtoken is None: if ldap_scopes != []: _ldaphelpers.update_singlevalue(dn, 'esiScope', None) if ldap_roles != []: _ldaphelpers.update_singlevalue(dn, 'corporationRole', None) # discord # this seems like the best place to put this logic if details['discordRefreshToken'] is not None: request_url = '/users/@me' code, result = common.request_esi.esi(__name__, request_url, method='get', charid=charid, version='v6', base='discord') if not code == 200: msg = 'unable to get discord user information for {0}: ({1}) {2}'.format(dn, code, result) logger.error(msg) return False discorduid = int(result.get('id')) discord2fa = result.get('mfa_enabled') discordname = result.get('username') # update/set discord UID and username if ldap_discorduid is None: _ldaphelpers.add_value(dn, 'discorduid', discorduid) elif ldap_discorduid != discorduid: _ldaphelpers.update_singlevalue(dn, 'discorduid', discorduid) if ldap_discordname is None: _ldaphelpers.add_value(dn, 'discordName', discordname) elif ldap_discordname != discordname: _ldaphelpers.update_singlevalue(dn, 'discordName', discordname) # update 2fa status if ldap_discord2fa is None: _ldaphelpers.add_value(dn, 'discord2fa', discord2fa) elif ldap_discord2fa != discord2fa: _ldaphelpers.update_singlevalue(dn, 'discord2fa', discord2fa) if details['esiRefreshToken'] is not None and 'esi-location.read_online.v1' in ldap_scopes and 1 is 2: # get online status if possible request_url = 'characters/{0}/online/'.format(charid) code, result = common.request_esi.esi(__name__, request_url, method='get', charid=charid, version='v2') if not code == 200: # it doesn't really matter msg = 'characters online API error {0}: {1}'.format(code, result) logger.error(msg) else: # example: # {'online': False, 'last_login': '******', 'last_logout': '2017-07-11T06:32:41Z', 'logins': 2474} last_login = time.strptime(result.get('last_login'), "%Y-%m-%dT%H:%M:%SZ") last_login = time.mktime(last_login) if ldap_lastlogin != last_login: _ldaphelpers.update_singlevalue(dn, 'lastLogin', last_login) ldap_lastlogin = last_login else: # so there's always a valid comparison if ldap_lastlogin != 0: _ldaphelpers.update_singlevalue(dn, 'lastLogin', 0) # affiliations information affilliations = _esihelpers.esi_affiliations(charid) if affilliations.get('error'): return False esi_allianceid = affilliations.get('allianceid') esi_alliancename = affilliations.get('alliancename') esi_corpid = affilliations.get('corpid') esi_corpname = affilliations.get('corpname') # what ldap thinks try: ldap_allianceid = int(details.get('alliance')) except Exception as e: ldap_allianceid = None try: ldap_corpid = int(details.get('corporation')) except Exception as e: ldap_corpid = None ldap_alliancename = details.get('allianceName') ldap_corpname = details.get('corporationName') # user's effective managable groups eff_groups = list( set(raw_groups) - safegroups ) # tinker with ldap to account for reality if not esi_allianceid == ldap_allianceid: # update a changed alliance id _ldaphelpers.update_singlevalue(dn, 'alliance', str(esi_allianceid)) if not esi_alliancename == ldap_alliancename: # update a changed alliance name _ldaphelpers.update_singlevalue(dn, 'allianceName', str(esi_alliancename)) if not esi_corpid == ldap_corpid: # update a changed corp id _ldaphelpers.update_singlevalue(dn, 'corporation', str(esi_corpid)) if not esi_corpname == ldap_corpname: # update a changed corp name _ldaphelpers.update_singlevalue(dn, 'corporationName', str(esi_corpname)) # GROUP MADNESS vanguard = vg_alliances() + vg_blues() if vanguard == False: return # NOT banned: if 'banned' not in raw_groups and status is not 'banned': # non-banned people should all have public # not sure how this happens if 'public' not in raw_groups: _ldaphelpers.add_value(dn, 'authGroup', 'public') # this character is blue but not marked as such if esi_allianceid in vg_alliances() or esi_allianceid in vg_blues() or esi_allianceid in vg_renters(): if not status == 'blue': _ldaphelpers.update_singlevalue(dn, 'accountStatus', 'blue') # pubbies do not need status. this does not affect alts meaningfully. if not esi_allianceid in vg_alliances() and esi_allianceid not in vg_blues() and esi_allianceid not in vg_renters(): if not status == 'public': _ldaphelpers.update_singlevalue(dn, 'accountStatus', 'public') if len(eff_groups) > 0: _ldaphelpers.purge_authgroups(dn, eff_groups) triumvirate = 933731581 # activity purge if ldap_lastlogin is None: ldap_lastlogin = 0 login_difference = time.time() - ldap_lastlogin if login_difference > 30*86400 and esi_allianceid == triumvirate: # logic to purge out of groups based on inactivity pass # _ldaphelpers.purge_authgroups(dn, eff_groups) # return # some checks for those marked blue already if status == 'blue': if 'vanguard' in eff_groups and esi_allianceid in vg_blues(): # a special case for people moving from vanguard to vg blues _ldaphelpers.purge_authgroups(dn, ['vanguard']) if 'vanguardBlue' in eff_groups and esi_allianceid in vg_alliances(): # a special case for people moving from vanguard blues to vanguard _ldaphelpers.purge_authgroups(dn, ['vanguardBlue']) if 'renters' not in eff_groups: if esi_allianceid in vg_renters(): # renters get renters _ldaphelpers.add_value(dn, 'authGroup', 'renters') if 'triumvirate' not in eff_groups: if esi_allianceid == triumvirate: # tri get trimvirate _ldaphelpers.add_value(dn, 'authGroup', 'triumvirate') if 'vanguardBlue' not in eff_groups: if esi_allianceid in vg_blues(): # vanguard blues get this _ldaphelpers.add_value(dn, 'authGroup', 'vanguardBlue') if 'vanguard' not in eff_groups: if esi_allianceid in vg_alliances(): # vanguard alliances get vanguard _ldaphelpers.add_value(dn, 'authGroup', 'vanguard') # purge shit from banned people if 'banned' in raw_groups: # purge off any groups you shouldn't have if len(eff_groups) > 0: _ldaphelpers.purge_authgroups(dn, eff_groups) if not status == 'banned': _ldaphelpers.update_singlevalue(dn, 'accountStatus', 'banned') if status == 'banned' and 'banned' not in raw_groups: # this shouldn't happen but this makes sure data stays synchronized # purge off any groups you shouldn't have if len(eff_groups) > 0: _ldaphelpers.purge_authgroups(dn, eff_groups) # make sure banned people don't have pending by accident if 'ban_pennding' in raw_groups: _ldaphelpers.purge_authgroups(dn, ['ban_pending']) return True
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 ldap_create_stub( charname=None, charid=None, isalt=False, altof=None, accountstatus='public', authgroups=['public'], atoken=None, rtoken=None ): # make a very basic ldap entry for charname function = __name__ user = dict() if charname is not None and charid is None: # making a stub based on a charid result = _esihelpers.user_search(charname) if result == False: # damage msg = 'ESI search error' return False, msg elif len(result) is 0: # nothing found. msg = 'no such character' return False, msg elif len(result) > 1: # too much found msg = 'too many results' return False, msg # okay hopefuly nothing else f****d up by now charid = result['character'] if charid is None: msg = 'no charname, no charid' return False, msg # get affiliations and shit affiliations = _esihelpers.esi_affiliations(charid) charname = affiliations.get('charname') corpid = affiliations.get('corpid') corpname = affiliations.get('corporation_name') allianceid = affiliations.get('allianceid') alliancename = affiliations.get('alliancename') cn, dn = ldap_normalize_charname(charname) user['uid'] = charid user['altof'] = altof user['characterName'] = charname user['corporation'] = corpid if allianceid: user['alliance'] = allianceid user['cn'] = cn user['sn'] = cn user['accountStatus'] = accountstatus user['authGroup'] = authgroups if rtoken: user['esiAccessToken'] = atoken user['esiRefreshToken'] = rtoken # build the stub attrs = [] # generate a bullshit password password = uuid.uuid4().hex password_hash = ldap_salted_sha1.hash(password) user['userPassword'] = password_hash # handle rest of crap for item in user.keys(): # authgroup is a repeated attribute so has to be handled carefully if item == 'authGroup': newgroups = [] for group in authgroups: group = str(group).encode('utf-8') newgroups.append(group) user['authGroup'] = newgroups else: user[item] = [ str(user[item]).encode('utf-8') ] attrs.append((item, user[item])) attrs.append(('objectClass', ['top'.encode('utf-8'), 'pilot'.encode('utf-8'), 'simpleSecurityObject'.encode('utf-8'), 'organizationalPerson'.encode('utf-8')])) # ldap binding ldap_conn = ldap.initialize(_ldap.ldap_host, bytes_mode=False) try: ldap_conn.simple_bind_s(_ldap.admin_dn, _ldap.admin_dn_password) except ldap.LDAPError as error: _logger.log('[' + __name__ + '] LDAP connection error: {}'.format(error),_logger.LogLevel.ERROR) return False, error # add the stub to ldap try: ldap_conn.add_s(dn, attrs) except Exception as error: return False, error finally: ldap_conn.unbind() # no error. done! return True, dn
def securitylog(function, action, charid=None, charname=None, ipaddress=None, date=None, detail=None): # log stuff into the security table import MySQLdb as mysql import common.credentials.database as _database import common.esihelpers as _esihelpers import common.logger as _logger import common.request_esi import time import urllib if date == None: date = time.time() friendly_time = time.asctime(time.localtime(date)) # mysql logging try: sql_conn = mysql.connect(database=_database.DB_DATABASE, user=_database.DB_USERNAME, password=_database.DB_PASSWORD, host=_database.DB_HOST) except mysql.Error as err: _logger.log('[' + __name__ + '] mysql error: ' + str(err), _logger.LogLevel.ERROR) cursor = sql_conn.cursor() # try to get character id if a charname (but no charid) is supplied if not charname == None and charid == None: query = { 'categories': 'character', 'language': 'en-us', 'search': charname, 'strict': 'true' } query = urllib.parse.urlencode(query) esi_url = 'search/?' + query code, result = common.request_esi.esi(__name__, esi_url, 'get') # not going to hardfail here, search is fuzzier than other endpoints try: charid = result['character'][0] except KeyError as error: _logger.log( '[' + function + '] unable to identify charname: {0}'.format(charname), _logger.LogLevel.WARNING) charid = None # try to get character name if a charid (but no charname) is supplied if charname == None and not charid == None: affiliations = _esihelpers.esi_affiliations(charid) charname = affiliations.get('charname') # log to file _logger.log( '[{0}] {1}: {2} ({3}) @ {4} {5}: {6}'.format(function, friendly_time, charname, charid, ipaddress, action, detail), _logger.LogLevel.INFO) # log to security table try: query = 'INSERT INTO Security (charID, charName, IP, action, date, detail) VALUES(%s, %s, %s, %s, FROM_UNIXTIME(%s), %s)' cursor.execute( query, ( charid, charname, ipaddress, action, date, detail, ), ) except mysql.Error as err: _logger.log('[' + __name__ + '] mysql error: ' + str(err), _logger.LogLevel.ERROR) return False finally: cursor.close() sql_conn.commit() sql_conn.close() return True
def notification_process(not_type, not_data, charid=None): # figure out what additional information needs to be requested from ESI or whatnot # in order to make these f*****g notifications make sense # f**k me so much crap data = {} # pocos if we ever give a shit: OrbitalReinforced / OrbitalAttacked if not_type == 'StructureLostShields': # example: # {'solarsystemID': 30001154, 'structureID': 1023478424724, 'timeLeft': 837701544696, #'structureShowInfoData': ['showinfo', 35832, 1023478424724], 'vulnerableTime': 9000000000} pass if not_type == 'StructureLostArmor': # same as StructureLostShields pass if not_type == 'StructureUnderAttack': # example: # {'allianceLinkData': ['showinfo', 16159, 498125261], 'shieldPercentage': 2.7245399818695597e-10, 'hullPercentage': 100.0, # 'solarsystemID': 30001154, 'structureShowInfoData': ['showinfo', 35832, 1023478424724], 'corpLinkData': ['showinfo', 2, 98210135], #'corpName': 'Infinite Point', 'structureID': 1023478424724, 'charID': 96554255, 'allianceID': 498125261, #'armorPercentage': 99.95246052415511, 'allianceName': 'Test Alliance Please Ignore'} corpLinkData = not_data.get('corpLinkData') # to keep compatable with the general method later data['status'] = round(not_data.get('shieldPercentage'), 2), round( not_data.get('armorPercentage'), 2), round(not_data.get('hullPercentage'), 2) data['attacker_affiliations'] = _esihelpers.esi_affiliations( not_data.get('charID')) if not_type == 'TowerAlertMsg': # example: # {'moonID': 40139259, 'shieldValue': 0.9999255519999989, 'aggressorCorpID': 803493697, 'aggressorID': 90936488, #'solarSystemID': 30002185, 'aggressorAllianceID': 498125261, 'typeID': 16214, 'hullValue': 1.0, 'armorValue': 1.0} data['moon_info'] = _esihelpers.moon_info(not_data.get('moonID')) data['attacker_affiliations'] = _esihelpers.esi_affiliations( not_data.get('aggressorID')) data['status'] = round(100 * not_data.get('shieldValue'), 2), 100, 100 if not_type == 'SovCommandNodeEventStarted': # example: # {'constellationID': 20000347, 'solarSystemID': 30002365, 'campaignEventType': 1} data['campaign_typeid'] = not_data.get('campaignEventType') data['campaign_type'] = sov_campaigns( not_data.get('campaignEventType')) if not_type == 'EntosisCaptureStarted': # example: # {'solarSystemID': 30000744, 'structureTypeID': 21646} pass if not_type == 'SovStructureDestroyed': # example: # {'solarSystemID': 30000825, 'structureTypeID': 32226} pass # common things # structure info if not_data.get('structureShowInfoData') is not None: data['structure_type_data'] = _esihelpers.type_info( not_data.get('structureShowInfoData')[1]) if not_data.get('structureTypeID') is not None: data['structure_type_data'] = _esihelpers.type_info( not_data.get('structureTypeID')) # structure ID for specific queries if not_data.get('structureID') is not None: data['structure_id'] = not_data.get('structureID') # fetch structure name request_url = 'universe/structures/{0}/'.format(data['structure_id']) code, result = common.request_esi.esi(__name__, request_url, version='v2', charid=charid) data['structure_name'] = result.get('name') # can get solar system id from corplinkdata but whatever data['solar_system_info'] = _esihelpers.solar_system_info( result['solar_system_id']) # system info if not_data.get('solarSystemID') is not None: data['solar_system_info'] = _esihelpers.solar_system_info( not_data.get('solarSystemID')) return data