net.algem.security.UserCtrl.java Source code

Java tutorial

Introduction

Here is the source code for net.algem.security.UserCtrl.java

Source

/*
 * @(#)UserCtrl.java    1.7.3 15/02/18
 *
 * Copyright (c) 2015-2018 Musiques Tangentes. All Rights Reserved.
 *
 * This file is part of Algem Web App.
 * Algem Web App is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Algem Web App is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Algem Web App. If not, see <http://www.gnu.org/licenses/>.
 *
 */
package net.algem.security;

import com.lowagie.text.BadElementException;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Font;
import com.lowagie.text.PageSize;
import com.lowagie.text.Phrase;
import com.lowagie.text.pdf.BaseFont;
import com.lowagie.text.pdf.PdfPCell;
import com.lowagie.text.pdf.PdfPTable;
import com.lowagie.text.pdf.PdfWriter;
import java.awt.Color;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.Principal;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import net.algem.config.Config;
import net.algem.config.ConfigKey;
import net.algem.contact.Email;
import net.algem.contact.Person;
import net.algem.planning.BookingScheduleElement;
import net.algem.planning.Hour;
import net.algem.planning.PlanningService;
import net.algem.planning.ScheduleElement;
import net.algem.planning.ScheduleRangeElement;
import net.algem.util.CommonDao;
import static net.algem.util.GemConstants.DATE_FORMAT;
import net.algem.util.Postit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.mail.MailException;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.FileCopyUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * Controller for login operations.
 *
 * @author <a href="mailto:jmg@musiques-tangentes.asso.fr">Jean-Marc Gobat</a>
 * @version 1.7.3
 * @since 1.0.0 11/02/13
 */
@Controller
//@Scope("session")
public class UserCtrl {

    private final static Logger LOGGER = Logger.getLogger(UserCtrl.class.getName());
    private final static Locale CTX_LOCALE = LocaleContextHolder.getLocale();

    @Value("#{organization}")
    private Map<String, String> organization;

    @Autowired
    @Qualifier("authenticationManager")
    AuthenticationManager authenticationManager;

    @Autowired
    SecurityContextRepository secCtxRepo;

    @Autowired
    private UserService service;

    @Autowired
    private PlanningService planningService;

    @Resource(name = "messageSource")
    private MessageSource messageSource;

    @Autowired
    private MailSender mailSender;

    @Autowired
    private SimpleMailMessage recoverMessage;

    public UserCtrl() {
    }

    public void setService(UserService service) {
        this.service = service;
    }

    public void setMailSender(MailSender mailSender) {
        this.mailSender = mailSender;
    }

    public void setRecoverMessage(SimpleMailMessage recoverMessage) {
        this.recoverMessage = recoverMessage;
    }

    @RequestMapping(method = RequestMethod.GET, value = "login.html")
    public String logIn() {
        return "login";
    }

    // Login form with error
    @RequestMapping("/login-error.html")
    public String loginError(Model model) {
        model.addAttribute("loginError", true);
        return "index";
    }

    /**
     * Manage ajax POST request.
     *
     * @param username username transmitted
     * @param password clear password
     * @param request httprequest
     * @param response httpresponse
     * @return JSON data as string
     */
    // IMPORTANT HERE if mapping ends with html : produces="text/html" !! AND NOT application/json or text/plain !
    //@RequestMapping(value = "/jxlogin.html", method = RequestMethod.POST, produces = "text/html;charset=UTF-8")
    @RequestMapping(value = "/jxlogin")
    @ResponseBody
    public String performLogin(@RequestParam("j_username") String username,
            @RequestParam("j_password") String password, HttpServletRequest request, HttpServletResponse response) {
        try {
            SecurityContext secuContext = SecurityContextHolder.getContext();
            //      UserDetails userDetails = (UserDetails) secuContext.getAuthentication().getPrincipal();

            Authentication auth = SecurityContextHolder.getContext().getAuthentication();
            if (auth != null && !(auth instanceof AnonymousAuthenticationToken) && auth.isAuthenticated()) {
                String msg = messageSource.getMessage("already.connected.label", null, CTX_LOCALE);
                return "{\"msg\":\"" + msg + "\"}";
            }
            UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username.trim(),
                    password);
            auth = authenticationManager.authenticate(token);
            secuContext.setAuthentication(auth);
            //      secCtxRepo.saveContext(secuContext, request, response);
            String msg = messageSource.getMessage("login.success.label", new Object[] { username }, CTX_LOCALE);
            //          rememberMeServices.loginSuccess(request, response, auth);
            return "{\"msg\":\"" + msg + "\"}";
        } catch (BadCredentialsException ex) {
            return "{\"status\": \"" + ex.getMessage() + "\"}";
        } catch (Exception other) {
            LOGGER.log(Level.SEVERE, null, other);
            return "{\"status\": \"" + other.getMessage() + "\"}";
        }
    }

    @RequestMapping(method = RequestMethod.GET, value = "perso/tuto.html")
    public String tuto() {
        return "tuto";
    }

    /**
     * Show home page.
     * @param p
     * @param model
     * @param _prs private postit read status
     * @return a view as string
     */
    @RequestMapping(method = RequestMethod.GET, value = "perso/home.html")
    public String showHome(Principal p, Model model,
            @CookieValue(value = "_PRS", defaultValue = "false") String _prs,
            @RequestParam(value = "section", required = false) Integer section) {

        User u = service.findUserByLogin(p.getName());
        model.addAttribute("user", u);
        List<BookingScheduleElement> bookings = planningService.getBookings(u.getId(), Integer.MAX_VALUE);
        model.addAttribute("bookings", bookings);
        model.addAttribute("person", service.getPersonFromUser(u.getId()));
        Date now = new Date();
        Calendar cal = Calendar.getInstance();
        cal.setTime(now);
        cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
        model.addAttribute("startOfW", cal.getTime());
        Config c = service.getConf(ConfigKey.DEFAULT_ESTABLISHMENT.getKey());
        model.addAttribute("e", c.getValue());

        if ("false".equals(_prs)) {
            List<Postit> postitList = planningService.getPostits(u.getId());
            if (u.isTeacher()) {
                postitList.addAll(planningService.getPostits(Postit.TEACHERS));
            }
            model.addAttribute("postitList", postitList);
        }

        return "dossier";
    }

    @RequestMapping(method = RequestMethod.GET, value = "/perso/jxBookings")
    public @ResponseBody List<BookingScheduleElement> getBookings(@RequestParam("idper") int idper,
            @RequestParam("key") int key) {
        return planningService.getBookings(idper, key);
    }

    @RequestMapping(method = RequestMethod.GET, value = "signup.html")
    public String signUp(User u) {
        return "signup";
    }

    @RequestMapping(method = RequestMethod.POST, value = "signup.html")
    public String doSignUp(@Valid User user, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return "signup";
        }
        try {
            Person p = null;
            try {
                p = service.getPersonFromUser(user.getId());
            } catch (EmptyResultDataAccessException e) {
                LOGGER.log(Level.SEVERE, null, e);
                bindingResult.rejectValue("id", "user.person.error", new Object[] { user.getId() }, "");
                return "signup";
            }
            List<User> others = service.exist(user);
            if (others != null) {
                for (User o : others) {
                    if (o.getId() == user.getId()) {
                        bindingResult.rejectValue("id", "",
                                messageSource.getMessage("user.exists", null, CTX_LOCALE));
                        return "signup";
                    } else if (o.getLogin() != null && o.getLogin().equals(user.getLogin())) {
                        bindingResult.rejectValue("login", "",
                                messageSource.getMessage("login.used", null, CTX_LOCALE));
                        return "signup";
                    }
                }
            }
            if (p == null) {
                bindingResult.rejectValue("id", "user.person.error", new Object[] { user.getId() }, "");
                return "signup";
            }
            if (p.getType() != Person.PERSON && p.getType() != Person.ROOM) {
                bindingResult.rejectValue("id", "user.type.error");
                return "signup";
            }

            if (!isEmailValid(p, user.getEmail())) {
                bindingResult.rejectValue("email", "user.email.error", new Object[] { user.getEmail() }, "");
                return "signup";
            }

            user.setTeacher(service.isTeacher(user.getId())); // detect user status
            service.create(user);
        } catch (DataAccessException | UserException ex) {
            LOGGER.log(Level.SEVERE, null, ex);
            bindingResult.rejectValue("id", "user.account.create.exception",
                    new Object[] { ex.getLocalizedMessage() }, ex.getMessage());
            return "signup";
        }

        return "redirect:perso/home.html";
    }

    /**
     * Gets the page for recovery password request.
     *
     * @param user
     * @return a view
     */
    @RequestMapping(method = RequestMethod.GET, value = "recover.html")
    public String recoverPassword(User user) {
        return "recover";
    }

    @RequestMapping(method = RequestMethod.POST, value = "recover.html")
    public String doRecoverPassword(@RequestParam String email, HttpServletRequest request, Model model) {
        User found = service.findUserByEmail(email);
        if (found == null) {
            model.addAttribute("errorMessage", messageSource.getMessage("unknown.user", null, CTX_LOCALE));
            return "recover";
        }

        String token = UUID.randomUUID().toString();
        //FIX https
        //String url = "http://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath();
        String url = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
                + request.getContextPath();

        try {
            service.setToken(found.getId(), token);
            sendRecoverMessage(url, token, found);
            model.addAttribute("message", messageSource.getMessage("recover.send.info", null, CTX_LOCALE));
        } catch (MailException | DataAccessException ex) {
            LOGGER.log(Level.SEVERE, null, ex);
            model.addAttribute("errorMessage", messageSource.getMessage("recover.send.exception",
                    new Object[] { ex.getMessage() }, CTX_LOCALE));
        }

        return "recover";
    }

    /**
     * Method called when the user clicked on the link sent by email.
     *
     * @param u
     * @param id
     * @param token
     * @param model
     * @return
     */
    @RequestMapping(method = RequestMethod.GET, value = "recover.html", params = { "id", "token" })
    public String recover(User u, @RequestParam("id") int id, @RequestParam("token") String token, Model model) {
        PasswordResetToken resetToken = null;
        try {
            resetToken = service.getToken(id);
            if (!token.equals(resetToken.getToken())) {
                model.addAttribute("message", messageSource.getMessage("recover.invalid.token", null, CTX_LOCALE));
                return "error";
            }
        } catch (EmptyResultDataAccessException ex) {
            model.addAttribute("message", messageSource.getMessage("recover.invalid.token",
                    new Object[] { ex.getMessage() }, CTX_LOCALE));
            return "error";
        } catch (DataAccessException ex) {
            LOGGER.log(Level.SEVERE, null, ex);
            model.addAttribute("message",
                    messageSource.getMessage("data.exception", new Object[] { ex.getMessage() }, CTX_LOCALE));
            return "error";
        }

        Calendar cal = Calendar.getInstance();
        cal.setTime(new Date(resetToken.getCreation()));
        cal.add(Calendar.DAY_OF_MONTH, 1);// 24h delay
        LOGGER.log(Level.INFO, "today" + new Date() + " token " + cal.getTime());
        if (new Date().after(cal.getTime())) {
            model.addAttribute("message", messageSource.getMessage("recover.expired.token", null, CTX_LOCALE));
            return "error";
        }

        u.setId(id);

        //    Authentication auth = new UsernamePasswordAuthenticationToken(
        //      user, null, userDetailsService.loadUserByUsername(user.getEmail()).getAuthorities());
        //    SecurityContextHolder.getContext().setAuthentication(auth);
        return "reset";
    }

    @RequestMapping(method = RequestMethod.POST, value = "passreset.html")
    public String doUpdatePassword(@Valid User user, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return "reset";
        }
        service.updatePassword(user.getId(), user.getPassword());
        return "login";
    }

    @RequestMapping(method = RequestMethod.GET, value = "/user.html")
    public String showUserACL(HttpServletRequest request, Model model) {
        List<Map<String, Boolean>> acl = service.getAcl(Integer.parseInt(request.getParameter("id")));
        model.addAttribute("acl", acl);
        return "dossier";
    }

    @RequestMapping(value = "/xmember")
    public @ResponseBody boolean isMember(Principal p, @RequestParam String start, @RequestParam String end) {
        return service.isMember(p.getName(), start, end);
    }

    @RequestMapping(method = RequestMethod.GET, value = "/perso/xFollowUpStudent")
    public @ResponseBody List<ScheduleElement> getFollowUp(@RequestParam("userId") String userId,
            @RequestParam("from") String from, @RequestParam("to") String to, Principal p) {
        return getFollowUpSchedules(userId, from, to);
    }

    @RequestMapping(value = "/perso/user/savePDF", method = RequestMethod.GET, produces = "application/pdf")
    public @ResponseBody void saveFollowUpAsPDF(@RequestParam String userId, @RequestParam String from,
            @RequestParam String to, HttpServletResponse response) throws IOException {

        try {
            File pdf = getFollowUpAsPDF(userId, from, to);
            InputStream in = new FileInputStream(pdf);
            response.setHeader("Content-Length", String.valueOf(pdf.length()));
            response.setHeader("Content-disposition", "attachment;filename=" + pdf.getName());
            response.setContentType("application/pdf");

            FileCopyUtils.copy(in, response.getOutputStream());
        } catch (DocumentException ex) {
            LOGGER.log(Level.SEVERE, null, ex);
        }
    }

    private List<ScheduleElement> getFollowUpSchedules(String userId, String from, String to) {
        Date dateFrom = null;
        Date dateTo = null;
        // !important : convert all numbers to long
        long max = 1000L * 60L * 60L * 24L * 365L;// 1year
        try {
            dateFrom = DATE_FORMAT.parse(from);
            dateTo = DATE_FORMAT.parse(to);
        } catch (ParseException ex) {
            LOGGER.log(Level.SEVERE, null, ex);
            dateFrom = new Date();
            dateTo = new Date();
        }

        if (dateFrom.after(dateTo)) {
            dateFrom.setTime(dateTo.getTime());
        }
        long interval = dateTo.getTime() - dateFrom.getTime();
        if (interval > max) {
            dateTo.setTime(dateFrom.getTime() + max);
        }

        try {
            return service.getFollowUp(Integer.parseInt(userId), dateFrom, dateTo);
        } catch (DataAccessException ex) {
            LOGGER.log(Level.SEVERE, null, ex);
        }
        return null;
    }

    private File getFollowUpAsPDF(String userId, String from, String to)
            throws IOException, BadElementException, DocumentException {
        String path = "/tmp/" + "suivi-" + userId + ".pdf";
        File f = new File(path);
        LOGGER.log(Level.INFO, f.getName());
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        Document document = new Document(PageSize.A4.rotate());
        PdfWriter.getInstance(document, byteArrayOutputStream); // Do this BEFORE document.open()
        document.open();

        PdfPTable table = new PdfPTable(10);
        table.setWidthPercentage(100);
        table.setWidths(new float[] { 1.1f, 1.2f, 0.6f, 1.5f, 1.5f, 2f, 0.5f, 0.5f, 1.9f, 1.9f });

        BaseFont bf = BaseFont.createFont(BaseFont.HELVETICA, BaseFont.WINANSI, false);
        BaseFont bfb = BaseFont.createFont(BaseFont.HELVETICA_BOLD, BaseFont.WINANSI, false);
        Font normalFont = new Font(bf, 10);
        Font boldFont = new Font(bfb, 10);

        String fromLabel = messageSource.getMessage("from.label", null, CTX_LOCALE);
        String toLabel = messageSource.getMessage("to.label", null, CTX_LOCALE);
        String prefix = messageSource.getMessage("follow-up.label", null, CTX_LOCALE) + " "
                + organization.get("name.label");
        String period = fromLabel.toLowerCase() + " " + from + " " + toLabel.toLowerCase() + " " + to;
        PdfPCell headerCell = new PdfPCell(new Phrase(prefix + " " + period, boldFont));

        headerCell.setBackgroundColor(Color.LIGHT_GRAY);
        headerCell.setColspan(10);
        table.addCell(headerCell);

        table.addCell(new PdfPCell(new Phrase(messageSource.getMessage("date.label", null, CTX_LOCALE), boldFont)));
        table.addCell(new PdfPCell(new Phrase(messageSource.getMessage("time.label", null, CTX_LOCALE), boldFont)));
        table.addCell(new PdfPCell(
                new Phrase(messageSource.getMessage("time.length.label", null, CTX_LOCALE), boldFont)));
        table.addCell(new PdfPCell(new Phrase(messageSource.getMessage("room.label", null, CTX_LOCALE), boldFont)));
        table.addCell(
                new PdfPCell(new Phrase(messageSource.getMessage("course.label", null, CTX_LOCALE), boldFont)));
        table.addCell(
                new PdfPCell(new Phrase(messageSource.getMessage("teacher.label", null, CTX_LOCALE), boldFont)));
        String abs = messageSource.getMessage("absence.label", null, CTX_LOCALE);
        table.addCell(new PdfPCell(new Phrase(abs != null ? abs.substring(0, 3) + "." : "", boldFont)));
        table.addCell(
                new PdfPCell(new Phrase(messageSource.getMessage("score.label", null, CTX_LOCALE), boldFont)));
        table.addCell(new PdfPCell(
                new Phrase(messageSource.getMessage("individual.logbook.label", null, CTX_LOCALE), boldFont)));
        table.addCell(new PdfPCell(
                new Phrase(messageSource.getMessage("collective.comment.label", null, CTX_LOCALE), boldFont)));

        fillPdfTable(table, getFollowUpSchedules(userId, from, to), normalFont);

        document.add(table);
        document.close();
        byte[] pdfBytes = byteArrayOutputStream.toByteArray();
        Files.write(Paths.get(path), pdfBytes);
        return f;
    }

    private void fillPdfTable(PdfPTable table, List<ScheduleElement> items, Font font) {
        for (ScheduleElement e : items) {
            List<ScheduleRangeElement> ranges = new ArrayList<ScheduleRangeElement>(
                    (Collection<? extends ScheduleRangeElement>) e.getRanges());

            for (ScheduleRangeElement r : ranges) {
                String status = CommonDao.getAbsenceFromNumberStatus(r.getFollowUp().getStatus());
                String note = r.getFollowUp().getNote();
                String content1 = r.getFollowUp().getContent();
                String content2 = e.getFollowUp().getContent();
                table.addCell(new Phrase(e.getDateFr().toString(), font));
                table.addCell(new Phrase(r.getStart() + "-" + r.getEnd(), font));
                table.addCell(new Phrase(new Hour(r.getLength()).toString(), font));
                table.addCell(new Phrase(e.getDetail().get("room").getName(), font));
                table.addCell(new Phrase(e.getDetail().get("course").getName(), font));
                table.addCell(new Phrase(e.getDetail().get("teacher").getName(), font));
                table.addCell(new Phrase(status, font));
                table.addCell(new Phrase(note == null ? "" : note, font));
                table.addCell(new Phrase(content1 == null ? "" : content1.replaceAll("[\r\n]", " "), font));
                table.addCell(new Phrase(content2 == null ? "" : content2.replaceAll("[\r\n]", " "), font));
            }
        }
    }

    private boolean isEmailValid(Person p, String email) {
        for (Email e : p.getEmails()) {
            if (e.getEmail().equals(email)) {
                return true;
            }
        }
        return false;
    }

    private void sendRecoverMessage(String path, String token, User user) throws MailException {
        // Create a thread safe "copy" of the template message and customize it
        SimpleMailMessage mail = new SimpleMailMessage(recoverMessage);
        String args = "/recover.html?id=" + user.getId() + "&token=" + token;
        String msg = messageSource.getMessage("recover.info", new Object[] { user.toString() }, CTX_LOCALE);
        String url = path + args;
        mail.setTo(user.getEmail());
        mail.setText(msg + url);
        mailSender.send(mail);
    }
}