Search in sources :

Example 1 with ClientSession

use of net.robinfriedli.aiode.entities.ClientSession in project aiode by robinfriedli.

the class SessionManagementEndpoint method fetchSession.

@GetMapping(path = "/fetch_session/{session_id}", produces = "application/json")
public ResponseEntity<SessionBean> fetchSession(@PathVariable(name = "session_id") String sessionId, HttpServletRequest request, HttpServletResponse response) {
    UUID uuid = UUID.fromString(sessionId);
    String remoteAddr = request.getRemoteAddr();
    Optional<ClientSession> foundClientSession = hibernateComponent.invokeWithSession(session -> queryBuilderFactory.find(ClientSession.class).where((cb, root) -> cb.and(cb.equal(root.get("sessionId"), uuid), cb.equal(root.get("ipAddress"), remoteAddr))).build(session).uniqueResultOptional());
    if (foundClientSession.isPresent()) {
        ClientSession clientSession = foundClientSession.get();
        long userId = clientSession.getUserId();
        long guildId = clientSession.getGuildId();
        long textChannelId = clientSession.getTextChannelId();
        Guild guild = shardManager.getGuildById(guildId);
        if (guild == null) {
            throw new MissingAccessException("Could not connect to guild, aiode is probably no longer a member.");
        }
        Member member = guild.getMemberById(userId);
        if (member == null) {
            throw new MissingAccessException("Could not connect to user, user is probably no longer a member.");
        }
        TextChannel textChannel = guild.getTextChannelById(textChannelId);
        if (textChannel == null) {
            textChannel = guildManager.getDefaultTextChannelForGuild(guild);
            TextChannel finalTextChannel = textChannel;
            hibernateComponent.consumeSession(session -> {
                clientSession.setTextChannelId(finalTextChannel.getIdLong());
                session.update(clientSession);
            });
        }
        setSessionCookie(response, sessionId);
        return ResponseEntity.ok(SessionBean.create(userId, member.getEffectiveName(), guildId, guild.getName(), textChannelId, textChannel.getName(), sessionId));
    }
    return ResponseEntity.status(HttpStatus.OK).build();
}
Also used : PathVariable(org.springframework.web.bind.annotation.PathVariable) MissingAccessException(net.robinfriedli.aiode.rest.exceptions.MissingAccessException) Member(net.dv8tion.jda.api.entities.Member) TextChannel(net.dv8tion.jda.api.entities.TextChannel) QueryBuilderFactory(net.robinfriedli.aiode.persist.qb.QueryBuilderFactory) HttpServletRequest(javax.servlet.http.HttpServletRequest) Guild(net.dv8tion.jda.api.entities.Guild) CriteriaBuilder(javax.persistence.criteria.CriteriaBuilder) CriteriaDelete(javax.persistence.criteria.CriteriaDelete) GetMapping(org.springframework.web.bind.annotation.GetMapping) Cookie(javax.servlet.http.Cookie) Root(javax.persistence.criteria.Root) ClientSession(net.robinfriedli.aiode.entities.ClientSession) PostMapping(org.springframework.web.bind.annotation.PostMapping) ShardManager(net.dv8tion.jda.api.sharding.ShardManager) HttpServletResponse(javax.servlet.http.HttpServletResponse) UUID(java.util.UUID) RestController(org.springframework.web.bind.annotation.RestController) HttpStatus(org.springframework.http.HttpStatus) GuildManager(net.robinfriedli.aiode.discord.GuildManager) GeneratedToken(net.robinfriedli.aiode.entities.GeneratedToken) SessionBean(net.robinfriedli.aiode.rest.SessionBean) Optional(java.util.Optional) ResponseEntity(org.springframework.http.ResponseEntity) HibernateComponent(net.robinfriedli.aiode.boot.configurations.HibernateComponent) TextChannel(net.dv8tion.jda.api.entities.TextChannel) ClientSession(net.robinfriedli.aiode.entities.ClientSession) MissingAccessException(net.robinfriedli.aiode.rest.exceptions.MissingAccessException) UUID(java.util.UUID) Guild(net.dv8tion.jda.api.entities.Guild) Member(net.dv8tion.jda.api.entities.Member) GetMapping(org.springframework.web.bind.annotation.GetMapping)

Example 2 with ClientSession

use of net.robinfriedli.aiode.entities.ClientSession in project aiode by robinfriedli.

the class ConnectCommand method doRun.

@Override
public void doRun() {
    CommandContext context = getContext();
    Guild guild = context.getGuild();
    User user = context.getUser();
    long guildId = guild.getIdLong();
    long userId = user.getIdLong();
    long textChannelId = context.getChannel().getIdLong();
    if (USERS_WITH_PENDING_CONNECTION.put(userId, DUMMY) != null) {
        throw new InvalidCommandException("The bot is already waiting to receive the token in the DMs.");
    }
    ClientSession clientSession = new ClientSession();
    clientSession.setGuildId(guildId);
    clientSession.setUserId(userId);
    clientSession.setTextChannelId(textChannelId);
    EmbedBuilder embedBuilder = new EmbedBuilder();
    embedBuilder.setDescription("Send me the token shown by the web client as a direct message");
    getMessageService().sendTemporary(embedBuilder, context.getChannel());
    awaitPrivateMessage(clientSession, userId, guild, user, 1);
}
Also used : EmbedBuilder(net.dv8tion.jda.api.EmbedBuilder) User(net.dv8tion.jda.api.entities.User) CommandContext(net.robinfriedli.aiode.command.CommandContext) InvalidCommandException(net.robinfriedli.aiode.exceptions.InvalidCommandException) ClientSession(net.robinfriedli.aiode.entities.ClientSession) Guild(net.dv8tion.jda.api.entities.Guild)

Example 3 with ClientSession

use of net.robinfriedli.aiode.entities.ClientSession in project aiode by robinfriedli.

the class ConnectCommand method awaitPrivateMessage.

private void awaitPrivateMessage(ClientSession clientSession, long userId, Guild guild, User user, int attemptNumber) {
    CompletableFuture<PrivateMessageReceivedEvent> futurePrivateMessage = getManager().getEventWaiter().awaitEvent(PrivateMessageReceivedEvent.class, event -> event.getAuthor().getIdLong() == userId).orTimeout(1, TimeUnit.MINUTES);
    CompletableFutures.handleWhenComplete(futurePrivateMessage, (event, error) -> {
        try {
            MessageService messageService = getMessageService();
            if (event != null) {
                String token = event.getMessage().getContentRaw();
                UUID sessionId;
                try {
                    sessionId = UUID.fromString(token);
                } catch (IllegalArgumentException e) {
                    String message = String.format("'%s' is not a valid token. ", token);
                    if (attemptNumber >= RETRY_COUNT) {
                        messageService.sendError(message + "Maximum retry count reached.", user);
                    } else {
                        messageService.sendError(message + String.format("Attempt %d / %d", attemptNumber, RETRY_COUNT), user);
                        awaitPrivateMessage(clientSession, userId, guild, user, attemptNumber + 1);
                    }
                    return;
                }
                QueryBuilderFactory queryBuilderFactory = getQueryBuilderFactory();
                MutexSyncMode<UUID> mutexSyncMode = new MutexSyncMode<>(sessionId, TOKEN_SYNC);
                HibernateInvoker.create().invokeConsumer(Mode.create().with(mutexSyncMode), session -> {
                    Optional<GeneratedToken> foundGeneratedToken = queryBuilderFactory.find(GeneratedToken.class).where((cb, root) -> cb.equal(root.get("token"), sessionId)).build(session).uniqueResultOptional();
                    if (foundGeneratedToken.isEmpty()) {
                        String message = "Token is invalid. Make sure it matches the token generated by your web client. Tokens may not be shared or reused. ";
                        if (attemptNumber >= RETRY_COUNT) {
                            messageService.sendError(message + "Maximum retry count reached.", user);
                        } else {
                            messageService.sendError(message + String.format("Attempt %d / %d", attemptNumber, RETRY_COUNT), user);
                            awaitPrivateMessage(clientSession, userId, guild, user, attemptNumber + 1);
                        }
                        return;
                    }
                    GeneratedToken generatedToken = foundGeneratedToken.get();
                    Long existingSessionCount = queryBuilderFactory.select(ClientSession.class, (from, cb) -> cb.count(from.get("pk")), Long.class).where((cb, root) -> cb.equal(root.get("sessionId"), sessionId)).build(session).uniqueResult();
                    if (existingSessionCount > 0) {
                        messageService.sendError("A session with this ID already exists. You are probably already signed in, try reloading your web client. If your client still can't sign in try generating a new token using the reload button and then try the connect command again.", user);
                        return;
                    }
                    clientSession.setLastRefresh(LocalDateTime.now());
                    clientSession.setSessionId(sessionId);
                    clientSession.setIpAddress(generatedToken.getIp());
                    session.persist(clientSession);
                    session.delete(generatedToken);
                    messageService.sendSuccess(String.format("Okay, a session connected to guild '%s' " + "has been prepared. You may return to the web client to complete the setup. The client should " + "connect automatically, else you can continue manually.", guild.getName()), user);
                });
            } else if (error != null) {
                if (error instanceof TimeoutException) {
                    messageService.sendError("Your connection attempt timed out", user);
                } else {
                    EmbedBuilder exceptionEmbed = ExceptionUtils.buildErrorEmbed(error);
                    exceptionEmbed.setTitle("Exception");
                    exceptionEmbed.setDescription("There has been an error awaiting private message to establish connection");
                    LoggerFactory.getLogger(getClass()).error("unexpected exception while awaiting private message", error);
                    sendMessage(exceptionEmbed.build());
                }
                setFailed(true);
            }
        } finally {
            USERS_WITH_PENDING_CONNECTION.remove(userId);
        }
    }, e -> {
        LoggerFactory.getLogger(getClass()).error("Unexpected error in whenComplete of event handler", e);
        EmbedBuilder embedBuilder = ExceptionUtils.buildErrorEmbed(e).setTitle("Exception").setDescription("There has been an unexpected exception while trying to establish the connection");
        getMessageService().send(embedBuilder.build(), user);
    });
}
Also used : LocalDateTime(java.time.LocalDateTime) LoggerFactory(org.slf4j.LoggerFactory) TimeoutException(java.util.concurrent.TimeoutException) CompletableFuture(java.util.concurrent.CompletableFuture) MutexSync(net.robinfriedli.exec.MutexSync) User(net.dv8tion.jda.api.entities.User) QueryBuilderFactory(net.robinfriedli.aiode.persist.qb.QueryBuilderFactory) Guild(net.dv8tion.jda.api.entities.Guild) Map(java.util.Map) CompletableFutures(net.robinfriedli.aiode.concurrent.CompletableFutures) CommandContribution(net.robinfriedli.aiode.entities.xml.CommandContribution) ClientSession(net.robinfriedli.aiode.entities.ClientSession) CommandManager(net.robinfriedli.aiode.command.CommandManager) Mode(net.robinfriedli.exec.Mode) MessageService(net.robinfriedli.aiode.discord.MessageService) InvalidCommandException(net.robinfriedli.aiode.exceptions.InvalidCommandException) ConcurrentHashMap(java.util.concurrent.ConcurrentHashMap) PrivateMessageReceivedEvent(net.dv8tion.jda.api.events.message.priv.PrivateMessageReceivedEvent) UUID(java.util.UUID) EmbedBuilder(net.dv8tion.jda.api.EmbedBuilder) HibernateInvoker(net.robinfriedli.aiode.function.HibernateInvoker) TimeUnit(java.util.concurrent.TimeUnit) AbstractCommand(net.robinfriedli.aiode.command.AbstractCommand) CommandContext(net.robinfriedli.aiode.command.CommandContext) GeneratedToken(net.robinfriedli.aiode.entities.GeneratedToken) Optional(java.util.Optional) MutexSyncMode(net.robinfriedli.exec.modes.MutexSyncMode) ExceptionUtils(net.robinfriedli.aiode.exceptions.ExceptionUtils) PrivateMessageReceivedEvent(net.dv8tion.jda.api.events.message.priv.PrivateMessageReceivedEvent) MessageService(net.robinfriedli.aiode.discord.MessageService) QueryBuilderFactory(net.robinfriedli.aiode.persist.qb.QueryBuilderFactory) EmbedBuilder(net.dv8tion.jda.api.EmbedBuilder) MutexSyncMode(net.robinfriedli.exec.modes.MutexSyncMode) GeneratedToken(net.robinfriedli.aiode.entities.GeneratedToken) ClientSession(net.robinfriedli.aiode.entities.ClientSession) UUID(java.util.UUID) TimeoutException(java.util.concurrent.TimeoutException)

Example 4 with ClientSession

use of net.robinfriedli.aiode.entities.ClientSession in project aiode by robinfriedli.

the class RequestInterceptorHandler method preHandle.

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    ThreadContext threadContext = ThreadContext.Current.get();
    threadContext.install(new RequestContext(request));
    try {
        Cookie[] cookies = request.getCookies();
        String sessionId = null;
        if (cookies != null) {
            sessionId = Arrays.stream(cookies).filter(c -> c.getName().equals("botify_session_id")).map(Cookie::getValue).findAny().orElse(null);
        }
        ClientSession clientSession = null;
        ExecutionContext executionContext = null;
        if (sessionId != null) {
            UUID uuid = UUID.fromString(sessionId);
            String remoteAddr = request.getRemoteAddr();
            Optional<ClientSession> existingClientSession = hibernateComponent.invokeWithSession(session -> queryBuilderFactory.find(ClientSession.class).where((cb, root) -> cb.and(cb.equal(root.get("sessionId"), uuid), cb.equal(root.get("ipAddress"), remoteAddr))).build(session).uniqueResultOptional());
            if (existingClientSession.isPresent()) {
                clientSession = existingClientSession.get();
                executionContext = setupExecutionContext(clientSession);
                if (executionContext == null) {
                    response.sendError(403, "Could not connect to guild or user. Either the bot is having connection issues or the bot is longer part of the connected guild or the connected member is no longer part of this guild.");
                    threadContext.clear();
                    return false;
                }
                ExecutionContext.Current.set(executionContext);
            }
        }
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            AuthenticationRequired methodAnnotation = handlerMethod.getMethodAnnotation(AuthenticationRequired.class);
            if (methodAnnotation != null) {
                if (clientSession == null) {
                    response.sendError(403, "This endpoint requires the client to be connected to an active Session");
                    threadContext.clear();
                    return false;
                }
                String[] requiredPermissions = methodAnnotation.requiredPermissions();
                if (requiredPermissions.length > 0) {
                    Member member = executionContext.getMember();
                    boolean unauthorized = Arrays.stream(requiredPermissions).noneMatch(perm -> securityManager.askPermission(perm, member));
                    if (unauthorized) {
                        response.sendError(403, String.format("Member '%s' does not have any of the required permissions: %s", member.getEffectiveName(), String.join(", ", requiredPermissions)));
                        threadContext.clear();
                        return false;
                    }
                }
            }
        }
    } catch (Exception e) {
        response.sendError(500, "Internal server error");
        threadContext.clear();
        throw e;
    }
    return true;
}
Also used : Cookie(javax.servlet.http.Cookie) Arrays(java.util.Arrays) JDA(net.dv8tion.jda.api.JDA) GuildContext(net.robinfriedli.aiode.discord.GuildContext) Member(net.dv8tion.jda.api.entities.Member) TextChannel(net.dv8tion.jda.api.entities.TextChannel) ExecutionContext(net.robinfriedli.aiode.concurrent.ExecutionContext) QueryBuilderFactory(net.robinfriedli.aiode.persist.qb.QueryBuilderFactory) HandlerMethod(org.springframework.web.method.HandlerMethod) HttpServletRequest(javax.servlet.http.HttpServletRequest) Guild(net.dv8tion.jda.api.entities.Guild) SecurityManager(net.robinfriedli.aiode.command.SecurityManager) ThreadContext(net.robinfriedli.aiode.concurrent.ThreadContext) HandlerInterceptor(org.springframework.web.servlet.HandlerInterceptor) Cookie(javax.servlet.http.Cookie) ClientSession(net.robinfriedli.aiode.entities.ClientSession) ShardManager(net.dv8tion.jda.api.sharding.ShardManager) AuthenticationRequired(net.robinfriedli.aiode.rest.annotations.AuthenticationRequired) HttpServletResponse(javax.servlet.http.HttpServletResponse) SessionFactory(org.hibernate.SessionFactory) UUID(java.util.UUID) SpotifyApi(se.michaelthelin.spotify.SpotifyApi) Component(org.springframework.stereotype.Component) GuildManager(net.robinfriedli.aiode.discord.GuildManager) Optional(java.util.Optional) HibernateComponent(net.robinfriedli.aiode.boot.configurations.HibernateComponent) ThreadContext(net.robinfriedli.aiode.concurrent.ThreadContext) HandlerMethod(org.springframework.web.method.HandlerMethod) ExecutionContext(net.robinfriedli.aiode.concurrent.ExecutionContext) ClientSession(net.robinfriedli.aiode.entities.ClientSession) AuthenticationRequired(net.robinfriedli.aiode.rest.annotations.AuthenticationRequired) UUID(java.util.UUID) Member(net.dv8tion.jda.api.entities.Member)

Aggregations

Guild (net.dv8tion.jda.api.entities.Guild)4 ClientSession (net.robinfriedli.aiode.entities.ClientSession)4 Optional (java.util.Optional)3 UUID (java.util.UUID)3 QueryBuilderFactory (net.robinfriedli.aiode.persist.qb.QueryBuilderFactory)3 Cookie (javax.servlet.http.Cookie)2 HttpServletRequest (javax.servlet.http.HttpServletRequest)2 HttpServletResponse (javax.servlet.http.HttpServletResponse)2 EmbedBuilder (net.dv8tion.jda.api.EmbedBuilder)2 Member (net.dv8tion.jda.api.entities.Member)2 TextChannel (net.dv8tion.jda.api.entities.TextChannel)2 User (net.dv8tion.jda.api.entities.User)2 ShardManager (net.dv8tion.jda.api.sharding.ShardManager)2 HibernateComponent (net.robinfriedli.aiode.boot.configurations.HibernateComponent)2 CommandContext (net.robinfriedli.aiode.command.CommandContext)2 GuildManager (net.robinfriedli.aiode.discord.GuildManager)2 GeneratedToken (net.robinfriedli.aiode.entities.GeneratedToken)2 InvalidCommandException (net.robinfriedli.aiode.exceptions.InvalidCommandException)2 LocalDateTime (java.time.LocalDateTime)1 Arrays (java.util.Arrays)1