<commit_before>package org.xcolab.view.pages.proposals.utils.voting;

import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;

import org.xcolab.client.admin.enums.ConfigurationAttributeKey;
import org.xcolab.client.contest.pojo.Contest;
import org.xcolab.client.members.MembersClient;
import org.xcolab.client.members.pojo.Member;
import org.xcolab.client.proposals.ProposalMemberRatingClient;
import org.xcolab.client.proposals.pojo.Proposal;
import org.xcolab.client.proposals.pojo.evaluation.members.ProposalVote;
import org.xcolab.entity.utils.notifications.proposal.ProposalVoteValidityConfirmation;
import org.xcolab.util.exceptions.InternalException;

import java.sql.Timestamp;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.regex.Pattern;

public class VoteValidator {

    private static final int VOTE_RECENCY_THRESHOLD_HOURS = 12;
    private static final int NAME_SIMILARITY_LEVENSHTEIN_THRESHOLD = 3;

    private final Member member;
    private final Proposal proposal;
    private final Contest contest;
    private final String remoteIp;
    private final ProposalMemberRatingClient proposalMemberRatingClient;

    public VoteValidator(Member member, Proposal proposal, Contest contest,
            String remoteIp, ProposalMemberRatingClient proposalMemberRatingClient) {
        this.member = member;
        this.proposal = proposal;
        this.contest = contest;
        this.remoteIp = remoteIp;
        this.proposalMemberRatingClient = proposalMemberRatingClient;
    }

    public ValidationResult validate() {
        if (member.isVerifiedAccount() || isEmailWhitelisted()) {
            return ValidationResult.VALID;
        }

        if (member.getIsEmailBounced()) {
            return ValidationResult.INVALID_BOUNCED_EMAIL;
        }

        if (isEmailBlacklisted()) {
            return ValidationResult.INVALID_BLACKLISTED;
        }

        List<Member> usersWithSharedIP = MembersClient.findMembersByIp(remoteIp);
        usersWithSharedIP.remove(member);
        if (usersWithSharedIP.isEmpty()) {
            return ValidationResult.VALID;
        }

        final ProposalVote vote = getVote(member);
        if (vote == null) {
            throw new InternalException("Could not retrieve vote");
        }

        ValidationResult result = ValidationResult.VALID;
        for (Member otherMember : usersWithSharedIP) {
            if (isNameSimilar(otherMember)) {
                vote.setIsValid(false);
                result = ValidationResult.INVALID_DUPLICATE;
                break;
            }
        }

        int recentVotesFromSharedIP = 0;
        for (Member otherMember : usersWithSharedIP) {
            final ProposalVote otherVote = getVote(otherMember);
            if (otherVote != null) {
                if (isRecentVote(otherVote)) {
                    recentVotesFromSharedIP++;
                }
            }

        }
        if (vote.getIsValid() && recentVotesFromSharedIP > 7) {
            vote.setIsValid(false);
            result = ValidationResult.AWAITING_RESPONSE;
            sendConfirmationMail(vote);
        }
        proposalMemberRatingClient.updateProposalVote(vote);
        return result;
    }

    private ProposalVote getVote(Member otherUser) {
        return proposalMemberRatingClient
                .getProposalVoteByProposalIdUserId(proposal.getProposalId(),
                        otherUser.getUserId());
    }

    private boolean isRecentVote(ProposalVote otherVote) {
        final DateTime otherVoteTime = new DateTime(otherVote.getCreateDate());
        return otherVoteTime.plusHours(VOTE_RECENCY_THRESHOLD_HOURS).isAfterNow();
    }

    private boolean isNameSimilar(Member otherUser) {
        return StringUtils.getLevenshteinDistance(member.getFirstName(),
                otherUser.getFirstName()) < NAME_SIMILARITY_LEVENSHTEIN_THRESHOLD
                && StringUtils.getLevenshteinDistance(member.getLastName(),
                otherUser.getLastName()) < NAME_SIMILARITY_LEVENSHTEIN_THRESHOLD;
    }

    private boolean isEmailWhitelisted() {
        final List<Pattern> emailWhitelist =
                ConfigurationAttributeKey.VOTING_EMAIL_VERIFICATION_WHITELIST.get();
        return emailWhitelist.stream()
                .anyMatch(pattern -> pattern.matcher(member.getEmailAddress()).find());
    }

    private boolean isEmailBlacklisted() {
        final List<Pattern> emailBlacklist =
                ConfigurationAttributeKey.VOTING_EMAIL_VERIFICATION_BLACKLIST.get();
        return emailBlacklist.stream()
                .anyMatch(pattern -> pattern.matcher(member.getEmailAddress()).find());
    }

    private void sendConfirmationMail(ProposalVote vote) {
        String confirmationToken = UUID.randomUUID().toString();
        vote.setConfirmationToken(confirmationToken);
        vote.setConfirmationEmailSendDate(new Timestamp(new Date().getTime()));
        new ProposalVoteValidityConfirmation(proposal, contest, member, confirmationToken)
                .sendEmailNotification();
    }

    public enum ValidationResult {
        VALID,
        INVALID_BLACKLISTED,
        INVALID_BOUNCED_EMAIL,
        INVALID_DUPLICATE,
        AWAITING_RESPONSE
    }
}
<commit_msg>[COLAB-1761] Fixed missing database persistence of vote validation for bounced emails<commit_after>package org.xcolab.view.pages.proposals.utils.voting;

import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;

import org.xcolab.client.admin.enums.ConfigurationAttributeKey;
import org.xcolab.client.contest.pojo.Contest;
import org.xcolab.client.members.MembersClient;
import org.xcolab.client.members.pojo.Member;
import org.xcolab.client.proposals.ProposalMemberRatingClient;
import org.xcolab.client.proposals.pojo.Proposal;
import org.xcolab.client.proposals.pojo.evaluation.members.ProposalVote;
import org.xcolab.entity.utils.notifications.proposal.ProposalVoteValidityConfirmation;
import org.xcolab.util.exceptions.InternalException;

import java.sql.Timestamp;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.regex.Pattern;

public class VoteValidator {

    private static final int VOTE_RECENCY_THRESHOLD_HOURS = 12;
    private static final int NAME_SIMILARITY_LEVENSHTEIN_THRESHOLD = 3;

    private final Member member;
    private final Proposal proposal;
    private final Contest contest;
    private final String remoteIp;
    private final ProposalMemberRatingClient proposalMemberRatingClient;

    public VoteValidator(Member member, Proposal proposal, Contest contest,
            String remoteIp, ProposalMemberRatingClient proposalMemberRatingClient) {
        this.member = member;
        this.proposal = proposal;
        this.contest = contest;
        this.remoteIp = remoteIp;
        this.proposalMemberRatingClient = proposalMemberRatingClient;
    }

    public ValidationResult validate() {
        ProposalVote vote = getVote(member);
        if (vote == null) {
            throw new InternalException("Could not retrieve vote");
        }
        final ValidationResult validationResult = getValidationResult(vote);
        if (validationResult != ValidationResult.VALID) {
            invalidateVote(vote);
        }
        return validationResult;
    }

    private ValidationResult getValidationResult(ProposalVote vote) {
        if (member.isVerifiedAccount() || isEmailWhitelisted()) {
            return ValidationResult.VALID;
        }

        if (member.getIsEmailBounced()) {
            invalidateVote(vote);
            return ValidationResult.INVALID_BOUNCED_EMAIL;
        }

        if (isEmailBlacklisted()) {
            invalidateVote(vote);
            return ValidationResult.INVALID_BLACKLISTED;
        }

        List<Member> usersWithSharedIP = MembersClient.findMembersByIp(remoteIp);
        usersWithSharedIP.remove(member);
        if (usersWithSharedIP.isEmpty()) {
            return ValidationResult.VALID;
        }

        ValidationResult result = ValidationResult.VALID;
        for (Member otherMember : usersWithSharedIP) {
            if (isNameSimilar(otherMember)) {
                invalidateVote(vote);
                result = ValidationResult.INVALID_DUPLICATE;
                break;
            }
        }

        int recentVotesFromSharedIP = 0;
        for (Member otherMember : usersWithSharedIP) {
            final ProposalVote otherVote = getVote(otherMember);
            if (otherVote != null) {
                if (isRecentVote(otherVote)) {
                    recentVotesFromSharedIP++;
                }
            }

        }
        if (vote.getIsValid() && recentVotesFromSharedIP > 7) {
            String confirmationToken = generateAndSetConfirmationToken(vote);
            new ProposalVoteValidityConfirmation(proposal, contest, member, confirmationToken)
                    .sendEmailNotification();
            result = ValidationResult.AWAITING_RESPONSE;
        }
        return result;
    }

    private void invalidateVote(ProposalVote vote) {
        vote.setIsValid(false);
        proposalMemberRatingClient.updateProposalVote(vote);
    }

    private ProposalVote getVote(Member votingMember) {
        return proposalMemberRatingClient
                .getProposalVoteByProposalIdUserId(proposal.getProposalId(),
                        votingMember.getUserId());
    }

    private boolean isRecentVote(ProposalVote otherVote) {
        final DateTime otherVoteTime = new DateTime(otherVote.getCreateDate());
        return otherVoteTime.plusHours(VOTE_RECENCY_THRESHOLD_HOURS).isAfterNow();
    }

    private boolean isNameSimilar(Member otherUser) {
        return StringUtils.getLevenshteinDistance(member.getFirstName(),
                otherUser.getFirstName()) < NAME_SIMILARITY_LEVENSHTEIN_THRESHOLD
                && StringUtils.getLevenshteinDistance(member.getLastName(),
                otherUser.getLastName()) < NAME_SIMILARITY_LEVENSHTEIN_THRESHOLD;
    }

    private boolean isEmailWhitelisted() {
        final List<Pattern> emailWhitelist =
                ConfigurationAttributeKey.VOTING_EMAIL_VERIFICATION_WHITELIST.get();
        return emailWhitelist.stream()
                .anyMatch(pattern -> pattern.matcher(member.getEmailAddress()).find());
    }

    private boolean isEmailBlacklisted() {
        final List<Pattern> emailBlacklist =
                ConfigurationAttributeKey.VOTING_EMAIL_VERIFICATION_BLACKLIST.get();
        return emailBlacklist.stream()
                .anyMatch(pattern -> pattern.matcher(member.getEmailAddress()).find());
    }

    private String generateAndSetConfirmationToken(ProposalVote vote) {
        String confirmationToken = UUID.randomUUID().toString();
        vote.setConfirmationToken(confirmationToken);
        vote.setConfirmationEmailSendDate(new Timestamp(new Date().getTime()));
        proposalMemberRatingClient.updateProposalVote(vote);
        return confirmationToken;
    }

    public enum ValidationResult {
        VALID,
        INVALID_BLACKLISTED,
        INVALID_BOUNCED_EMAIL,
        INVALID_DUPLICATE,
        AWAITING_RESPONSE
    }
}

{'fraction_non_alphanumeric': 0.07992498660475085, 'fraction_numerical': 0.0014288265761743168, 'mean_word_length': 2.903450679679331, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1467707', 'n_tokens_mistral': 3211, 'n_tokens_neox': 3039, 'n_words': 524}