def reset_clearance(usr: User, save: bool = False): """ Resets a user's clearance. If a user is the CEO of its corporation, then a clearance level of 2 is granted. If its corporation is the executor of the alliance, then a level of 4 is granted instead. If the user is root or has a clearance level of 10, then a level of 10 is applied (so that superusers are preserved no matter what). Otherwise, the user's clearance level is set to 0. """ if usr.clearance_level >= 9: return if usr.character_id == 0: usr.clearance_level = 10 elif usr.is_ceo_of_alliance(): usr.clearance_level = 4 elif usr.is_ceo_of_corporation(): usr.clearance_level = 2 elif usr.clearance_level >= 0: usr.clearance_level = 0 if save: logging.debug( "Reset clearance level of %s to %d", usr.character_name, usr.clearance_level, ) usr.save()
def complete_authentication_challenge(connection: TS3Connection, usr: User): """ Complete an authentication challenge, see :meth:`sni.teamspeak.new_authentication_challenge`. """ challenge: TeamspeakAuthenticationChallenge = TeamspeakAuthenticationChallenge.objects.get( user=usr) client = find_client(connection, nickname=challenge.challenge_nickname) usr.teamspeak_cldbid = client.client_database_id usr.save() auth_group = ensure_autogroup(conf.teamspeak.auth_group_name) auth_group.modify(add_to_set__members=usr) challenge.delete() logging.info("Completed authentication challenge for %s", usr.character_name)
def index_users_skillpoints(): """ Measures all users skillpoints """ for usr in User.objects(): if has_esi_scope(usr, EsiScope.ESI_SKILLS_READ_SKILLS_V1): scheduler.add_job(index_user_skillpoints, args=(usr, ))
def token_has_enough_scopes(access_token: DecodedAccessToken, usr: User) -> bool: """ Tells wether the access token has all the cropes that are required for a given user. """ return usr.cumulated_mandatory_esi_scopes() <= set(access_token.scp)
def index_users_wallets(): """ Indexes user wallet balance """ for usr in User.objects(): if has_esi_scope(usr, EsiScope.ESI_WALLET_READ_CHARACTER_WALLET_V1): scheduler.add_job(index_user_wallets, args=(usr, ))
def index_users_mails(): """ Index all user emails. """ for usr in User.objects(): if has_esi_scope(usr, EsiScope.ESI_MAIL_READ_MAIL_V1): scheduler.add_job(index_user_mails, args=(usr, ))
def index_users_location(): """ Indexes all user's location """ for usr in User.objects(): if (has_esi_scope(usr, EsiScope.ESI_LOCATION_READ_LOCATION_V1) and has_esi_scope(usr, EsiScope.ESI_LOCATION_READ_ONLINE_V1) and has_esi_scope(usr, EsiScope.ESI_LOCATION_READ_SHIP_TYPE_V1)): scheduler.add_job(index_user_location, args=(usr, ))
async def update_discord_users(): """ Updates all discord users. """ for usr in User.objects(discord_user_id__ne=None): await utils.catch_all_async( update_discord_user, f"Could not update Discord properties of user {usr.character_name}", args=[usr], )
def get_user(tkn: Token = Depends(from_authotization_header_nondyn)): """ Returns the list of all user names. Requires a clearance level of 0 or more. """ assert_has_clearance(tkn.owner, "sni.read_user") return [ GetUserShortOut.from_record(usr) for usr in User.objects( clearance_level__gte=0).order_by("character_name") ]
def tracking_status(usr: User) -> TrackingStatus: """ Reports the tracking status of this user, see :class:`sni.esi.token.TrackingStatus` """ query_set = EsiRefreshToken.objects(owner=usr) if query_set.count() == 0: return TrackingStatus.HAS_NO_REFRESH_TOKEN cumulated_mandatory_esi_scopes = usr.cumulated_mandatory_esi_scopes() for refresh_token in query_set: if cumulated_mandatory_esi_scopes <= set(refresh_token.scopes): return TrackingStatus.HAS_A_VALID_REFRESH_TOKEN return TrackingStatus.ONLY_HAS_INVALID_REFRESH_TOKEN
def is_authorized_to_login(usr: User) -> bool: """ Tells wether a user is authorized to login or not. A user is authorized to login if the following conditions are satisfied: * its ``authorized_to_login`` field is ``True`` or if the user belongs to **at least one** group, corporation, alliance, or coalition whose ``authorized_to_login`` field is ``True``; * the user's ``authorized_to_login`` field is not ``False``, and none of the groups, corporation, alliance, or coalitions the user belongs to has a ``authorized_to_login`` field set ot ``False``. In addition, the root user is always allowed to login, regardless of the status of the group he's part of. """ if usr.character_id == 0: return True if usr.authorized_to_login is False: return False authorized_to_login: Optional[bool] = usr.authorized_to_login if usr.corporation is not None: if usr.corporation.authorized_to_login is False: return False # Note that in Python, (None or True) == True authorized_to_login = (authorized_to_login or usr.corporation.authorized_to_login) if usr.alliance is not None: if usr.alliance.authorized_to_login is False: return False authorized_to_login = (authorized_to_login or usr.alliance.authorized_to_login) for coa in usr.coalitions(): if coa.authorized_to_login is False: return False authorized_to_login = authorized_to_login or coa.authorized_to_login for grp in Group.objects(members=usr): if grp.authorized_to_login is False: return False authorized_to_login = authorized_to_login or grp.authorized_to_login # If authorized_to_login is still None, then the result is False return bool(authorized_to_login)
def from_record(usr: User) -> "GetUserOut": """ Populates a new :class:`sni.routers.user.GetUserOut` with the information contained in a user database record. """ return GetUserOut( alliance=(usr.alliance.alliance_id if usr.alliance is not None else None), authorized_to_login=usr.authorized_to_login, available_esi_scopes=list(available_esi_scopes(usr)), character_id=usr.character_id, character_name=usr.character_name, clearance_level=usr.clearance_level, coalitions=[str(coalition.pk) for coalition in usr.coalitions()], corporation=(usr.corporation.corporation_id if usr.corporation is not None else None), created_on=usr.created_on, cumulated_mandatory_esi_scopes=list( usr.cumulated_mandatory_esi_scopes()), is_ceo_of_alliance=usr.is_ceo_of_alliance(), is_ceo_of_corporation=usr.is_ceo_of_corporation(), tickered_name=usr.tickered_name, updated_on=usr.updated_on, )
def post_history_character_location_now( character_id: int, tkn: Token = Depends(from_authotization_header_nondyn), ): """ Get and save the current location of a character. Requires having clearance to access the ESI scopes ``esi-location.read_location.v1``, ``esi-location.read_online.v1``, and ``esi-location.read_ship_type.v1``, of the character. """ usr: User = User.objects(character_id=character_id).get() assert_has_clearance(tkn.owner, "esi-location.read_location.v1", usr) assert_has_clearance(tkn.owner, "esi-location.read_online.v1", usr) assert_has_clearance(tkn.owner, "esi-location.read_ship_type.v1", usr) location = get_user_location(usr) location.save() return GetCharacterLocationOut.from_record(location)
def get_history_character_wallet( character_id: int, response: Response, page: pdt.PositiveInt = Header(1), tkn: Token = Depends(from_authotization_header_nondyn), ): """ Get the wallet balance history of a character. Requires having clearance to access the ESI scope ``esi-wallet.read_character_wallet.v1`` of the character. The results are sorted by most to least recent, and paginated by pages of 50 items. The page count in returned in the ``X-Pages`` header. """ usr: User = User.objects(character_id=character_id).get() assert_has_clearance(tkn.owner, "esi-wallet.read_character_wallet.v1", usr) query_set = EsiWalletBalance.objects(user=usr).order_by("-timestamp") return [ GetCharacterWalletBalanceOut.from_record(document) for document in paginate(query_set, 50, page, response) ]
def delete_corporation_guest( corporation_id: int, character_id: int, tkn: Token = Depends(from_authotization_header_nondyn), ): """ Deletes a corporation guest """ corporation: Corporation = Corporation.objects( corporation_id=corporation_id ).get() assert_has_clearance( tkn.owner, "sni.delete_corporation_guest", corporation.ceo ) guest: User = User.objects( character_id=character_id, clearance_level__lt=0, corporation=corporation, ).get() guest.delete()
def get_history_character_mails( character_id: int, response: Response, page: pdt.PositiveInt = Header(1), tkn: Token = Depends(from_authotization_header_nondyn), ): """ Get the email history of a character. Requires having clearance to access the ESI scope ``esi-mail.read_mail.v1`` of the character. The results are sorted by most to least recent, and paginated by pages of 50 items. The page count in returned in the ``X-Pages`` header. Todo: Only show email sent by the specified user... """ usr: User = User.objects(character_id=character_id).get() assert_has_clearance(tkn.owner, "esi-mail.read_mail.v1", usr) query_set = EsiMail.objects(from_id=character_id).order_by("-timestamp") return [ GetCharacterMailShortOut.from_record(mail) for mail in paginate(query_set, 50, page, response) ]
def message_registered_clients_with_wrong_name(): """ Iterates through all users that are registered in Teamspeak, and checks their nickname. If it doesn't match, sends a message. """ connection = new_teamspeak_connection() for ts_client in client_list(connection): if ts_client.client_nickname.startswith(conf.teamspeak.bot_name): # TS client is probably the bot continue usr: User = User.objects( teamspeak_cldbid=ts_client.client_database_id).first() if usr is None: # TS client is unknown continue tickered_name = usr.tickered_name if ts_client.client_nickname != tickered_name: logging.debug( "Wrong nickname found %s; should be %s", ts_client.client_nickname, tickered_name, ) message = ( f"Hello {ts_client.client_nickname}. " + "Please change your Teamspeak nickname to " + f'"{tickered_name}" (without the quotes). Thank you :)' + " -- SeAT Navy Issue Teamspeak Bot; This is an automated" + " message, please do not respond.") utils.catch_all( connection.sendtextmessage, f"Failed to notify teamspeak user {ts_client.client_nickname}", kwargs={ "targetmode": 1, "target": ts_client.clid, "msg": message, }, ) close_teamspeak_connection(connection)
def get_history_character_location( character_id: int, response: Response, page: pdt.PositiveInt = Header(1), tkn: Token = Depends(from_authotization_header_nondyn), ): """ Get the location history of a character. Requires having clearance to access the ESI scopes ``esi-location.read_location.v1``, ``esi-location.read_online.v1``, and ``esi-location.read_ship_type.v1``, of the character. The results are sorted by most to least recent, and paginated by pages of 50 items. The page count in returned in the ``X-Pages`` header. """ usr: User = User.objects(character_id=character_id).get() assert_has_clearance(tkn.owner, "esi-location.read_location.v1", usr) assert_has_clearance(tkn.owner, "esi-location.read_online.v1", usr) assert_has_clearance(tkn.owner, "esi-location.read_ship_type.v1", usr) query_set = EsiCharacterLocation.objects(user=usr).order_by("-timestamp") return [ GetCharacterLocationOut.from_record(location) for location in paginate(query_set, 50, page, response) ]
async def auth(ctx: Context, code: str): """ Starts a Discord authentication challenge. """ if ctx.channel.id != conf.discord.auth_channel_id: return member: Member = ctx.author try: complete_authentication_challenge(member, code) usr: User = User.objects(discord_user_id=member.id).get() await update_discord_user(usr) logging.info( "Successfully authenticated user %s (%d)", ctx.author.name, ctx.author.id, ) await ctx.channel.send( f"Hi <@{ctx.author.id}> :wave:, you are now authenticated. " "Welcome aboard!") await log( f":white_check_mark: Successfully authenticated <@{ctx.author.id}>" ) except Exception as error: logging.error( "Could not authenticate Discord user %s (%d): %s", ctx.author.name, ctx.author.id, str(error), ) await ctx.channel.send( f":x: Sorry <@{ctx.author.id}>, I could not authenticate you. " "Please try again with a different code. If the problem persists, " "contact your Discord administrator.") await log( f":x: Failed to authenticate <@{ctx.author.id}>: {str(error)}") await ctx.message.delete()