001/*
002 * Stallion Core: A Modern Web Framework
003 *
004 * Copyright (C) 2015 - 2016 Stallion Software LLC.
005 *
006 * This program is free software: you can redistribute it and/or modify it under the terms of the
007 * GNU General Public License as published by the Free Software Foundation, either version 2 of
008 * the License, or (at your option) any later version. This program is distributed in the hope that
009 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
011 * License for more details. You should have received a copy of the GNU General Public License
012 * along with this program.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>.
013 *
014 *
015 *
016 */
017
018package io.stallion.forms;
019
020import io.stallion.exceptions.ClientException;
021import io.stallion.requests.validators.SafeMerger;
022import io.stallion.restfulEndpoints.ObjectParam;
023import io.stallion.services.LocalMemoryCache;
024import io.stallion.utils.Encrypter;
025import org.apache.commons.lang3.StringUtils;
026
027import javax.ws.rs.POST;
028import javax.ws.rs.Path;
029
030import static io.stallion.utils.Literals.*;
031import static io.stallion.Context.*;
032
033
034public class SimpleFormEndpoints {
035
036    @POST
037    @Path("/contacts/submit-form")
038    public Boolean submitForm(@ObjectParam(targetClass = SimpleFormSubmission.class) SimpleFormSubmission rawSubmission) {
039        SimpleFormSubmission submission = SafeMerger
040                .with()
041                .nonEmpty("antiSpamToken", "pageUrl", "data")
042                .optionalEmail("email")
043                .optional("pageTitle", "formId")
044                .merge(rawSubmission);
045
046        /* The Anti-spam token is an encrypted token with a milliseconds timestamp and a randomly generated key
047           This prevents a spammer from simply hitting this endpoint over and over again with a script. A given
048           random key can only be used once within an hour, and all tokens expire after an hour, so all together
049           it is not possible to submit more than once.
050
051           This will not stop a spammer who is actually requesting a new copy of the page and who is
052           parsing out the spam token single every time. We would have to implement IP address throttling or
053           captchas to fix that.
054         */
055
056        String token = Encrypter.decryptString(settings().getAntiSpamSecret(), submission.getAntiSpamToken());
057        if (empty(token) || !token.contains("|")) {
058            throw new ClientException("Anti-spam token is not in the correct format");
059        }
060        String[] parts = StringUtils.split(token, "|", 2);
061        Long time = Long.parseLong(parts[0]);
062        String randomKey = parts[1];
063
064        if (time == null || ((time + 60 * 60 * 1000) < mils())) {
065            throw new ClientException("Anti-spam token has expired. Please reload the page and submit again.");
066        }
067
068        Integer submissionCount = or((Integer)LocalMemoryCache.get("form_submissions", randomKey), 0);
069        if (submissionCount > 0) {
070                throw new ClientException("You have already submitted this form once.");
071        }
072
073
074        submission
075                .setSubmittedAt(mils());
076        SimpleFormSubmissionController.instance().save(submission);
077        if (!empty(submission.getEmail())) {
078            SimpleFormSubmissionEmailTask.enqueue(submission);
079        }
080
081        // Store a record of this token, so it cannot be reused
082        LocalMemoryCache.set("form_submissions", randomKey, submissionCount + 1, 90 * 60 * 1000);
083
084        return true;
085    }
086}