Java tutorial
/** * Copyright 2013 Stockholm County Council * * This file is part of APIGW * * APIGW is free software; you can redistribute it and/or modify * it under the terms of version 2.1 of the GNU Lesser General Public * License as published by the Free Software Foundation. * * APIGW 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with APIGW; if not, write to the * Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, MA 02111-1307 USA * */ package org.apigw.authserver.web.controller; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.math.BigInteger; import java.security.Security; import java.security.cert.X509Certificate; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.imageio.ImageIO; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; import org.apigw.appmanagement.ApplicationManagementService; import org.apigw.appmanagement.domain.Application; import org.apigw.appmanagement.domain.Application.State; import org.apigw.appmanagement.domain.Certificate; import org.apigw.appmanagement.domain.Developer; import org.apigw.authserver.svc.PermissionServices; import org.apigw.authserver.types.domain.Permission; import org.apigw.authserver.types.domain.User; import org.apigw.authserver.web.validator.ApplicationValidator; import org.apigw.authserver.web.validator.DeveloperValidator; import org.apigw.commons.logging.CitizenLoggingUtil; import org.bouncycastle.openssl.PEMReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Controller; import org.springframework.transaction.annotation.Transactional; import org.springframework.ui.ModelMap; import org.springframework.validation.BindingResult; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.ModelAttribute; 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.SessionAttributes; import org.springframework.web.client.RestTemplate; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.support.ByteArrayMultipartFileEditor; import org.springframework.web.servlet.ModelAndView; @Controller @SessionAttributes @Transactional @RequestMapping(value = "developer") public class ApplicationManagementController { private static final Logger log = LoggerFactory.getLogger(ApplicationManagementController.class); @Autowired private ApplicationManagementService appManagement; @Autowired private PermissionServices permissionServices; @Autowired private DeveloperValidator developerValidator; @Autowired private ApplicationValidator applicationValidator; @Autowired private JavaMailSender sender; @Autowired private CitizenLoggingUtil citizenLoggingUtil; @Value("${applicationmanagement.notification.mail.to}") private String mailAddress; @Value("${applicationmanagement.notification.mail.from}") private String mailFrom; @Value("${monitoring.api.location}") private String location;// = "http://localhost:9000"; private RestTemplate template = new RestTemplate(); @InitBinder public void initBinder(WebDataBinder binder) { binder.registerCustomEditor(byte[].class, new ByteArrayMultipartFileEditor()); } @RequestMapping("") public ModelAndView listApps(Authentication authentication) { UserDetails user = (UserDetails) authentication.getPrincipal(); log.debug("Logged in user is: {}", user.getUsername()); if (!developerExists(user)) { return new ModelAndView("redirect:/developer/register_developer"); } else { // ...otherwise, list the developers applications TreeMap<String, Object> model = new TreeMap<String, Object>(); List<Application> apps = appManagement.getApplications(user.getUsername()); Map<String, Integer> success = new HashMap<String, Integer>(); Map<String, Integer> client_failure = new HashMap<String, Integer>(); Map<String, Integer> server_failure = new HashMap<String, Integer>(); for (Application app : apps) { // load statistics if client is in production if (app.getState() == State.IN_PRODUCTION) { String id = app.getClientId(); success.put(id, getStats(id, "SUCCESS")); client_failure.put(id, getStats(id, "CLIENT_FAILURE")); server_failure.put(id, getStats(id, "SERVER_FAILURE")); } } model.put("apps", appManagement.getApplications(user.getUsername())); // TODO: cache this... Map<String, String> scopes = new HashMap<String, String>(); for (Permission permission : permissionServices.getAllPermissions()) { scopes.put(permission.getName(), permission.getDescription()); } model.put("scopes", scopes); model.put("success", success); model.put("client_failure", client_failure); model.put("server_failure", server_failure); return new ModelAndView("applications", model); } } @SuppressWarnings("unchecked") private int getStats(String id, String state) { List<Map<String, Object>> stats = template.getForObject(location + "/api/timeline/resourceRequestCount?categories=crm-scheduling," + id + "," + state + "&count=1", List.class); if (stats != null && stats.size() > 0) { return (Integer) stats.get(0).get("value"); } else { return 0; } } @RequestMapping("register_developer") public ModelAndView registerDeveloper(ModelMap model, Authentication authentication) { // send the developer to the register account page if not already registred UserDetails user = (UserDetails) authentication.getPrincipal(); String fullname = (user instanceof User) ? ((User) user).getFullName() : ""; Developer developer = new Developer(); developer.setResidentIdentificationNumber(user.getUsername()); developer.setName(fullname); model.addAttribute(developer); model.addAttribute("registered", false); return new ModelAndView("register_developer"); } @RequestMapping("edit_developer") public ModelAndView editDeveloper(ModelMap model, Authentication authentication) { UserDetails user = (UserDetails) authentication.getPrincipal(); Developer developer = appManagement.getDeveloper(user.getUsername()); if (developer != null) { model.addAttribute(developer); model.addAttribute("registered", true); return new ModelAndView("register_developer"); } else { return registerDeveloper(model, authentication); } } @RequestMapping(value = "register_developer", method = RequestMethod.POST) public String registerDeveloper(@ModelAttribute("developer") Developer developer, BindingResult result, Authentication authentication) { developerValidator.validate(developer, result); if (result.hasErrors()) { return "register_developer"; } else { UserDetails user = (UserDetails) authentication.getPrincipal(); log.debug("Logged in user is: {}", citizenLoggingUtil.getLogsafeSSN(user.getUsername())); if (developer.getResidentIdentificationNumber() == null) { developer.setResidentIdentificationNumber(user.getUsername()); } else if (!developer.getResidentIdentificationNumber().equals(user.getUsername())) { throw new RuntimeException( "The provided resident identification number isn't the same as the number for the logged in user"); } String fullname = (user instanceof User) ? ((User) user).getFullName() : ""; if (!fullname.equals(developer.getName())) { developer.setName(fullname); } appManagement.registerDeveloper(developer); return "redirect:"; } } @RequestMapping(value = "/app", method = RequestMethod.GET, params = { "edit" }) public ModelAndView editApp(@RequestParam("edit") Long id, ModelMap model, Authentication authentication) { Application application = appManagement.getApplication(id); UserDetails user = (UserDetails) authentication.getPrincipal(); if (application == null) { throw new IllegalArgumentException("No application found with id " + id); } if (user.getUsername().equals(application.getDeveloper().getResidentIdentificationNumber())) { List<Permission> permissions = permissionServices.getAllPermissions(); model.addAttribute("editIcon", false); model.addAttribute(application); model.addAttribute("scopes", permissions); return new ModelAndView("application/edit"); } else { throw new IllegalArgumentException("Application developer is not the same as the logged in user"); } } @RequestMapping(value = "/edit", method = RequestMethod.POST) public ModelAndView editApp(ModelMap model, Authentication authentication, @ModelAttribute("application") Application application, MultipartFile icon, @RequestParam(value = "certificate", required = false) MultipartFile certificate, BindingResult result) { UserDetails user = (UserDetails) authentication.getPrincipal(); Developer developer = appManagement.getDeveloper(user.getUsername()); List<Permission> permissions = permissionServices.getAllPermissions(); log.debug("User {} registered or edited the app {}.", citizenLoggingUtil.getLogsafeSSN(user.getUsername()), application.getName()); if (developer == null) { return new ModelAndView("redirect:register_developer"); } else { applicationValidator.validate(application, result); log.info("app id: " + application.getId()); Application dbApp = appManagement.getApplication(application.getId()); if (!user.getUsername().equals(dbApp.getDeveloper().getResidentIdentificationNumber())) { throw new IllegalArgumentException( "The application developer is not the same as the logged in user"); } if (application.getIcon() == null && dbApp.getIcon() != null && dbApp.getIcon().length > 0) { application.setIcon(dbApp.getIcon()); log.debug("Icon wasn't updated this time around"); } else if (application.getIcon() != null) { log.debug("Icon was updated"); try { ByteArrayInputStream bis = new ByteArrayInputStream(application.getIcon()); BufferedImage bufferedImage = ImageIO.read(bis); log.info("Width: " + bufferedImage.getWidth() + " Height: " + bufferedImage.getHeight()); application.setIconContentType(icon.getContentType()); // TODO: Check width and height here! } catch (Exception e) { result.rejectValue("icon", "invalid.icon", "Ikonen r ej giltig"); } } application.setCertificates(dbApp.getCertificates()); application.setRegistrationDate(dbApp.getRegistrationDate()); application.setState(dbApp.getState()); application.setDeveloper(developer); //For now we are just allowing the addition of just one certificate List<Certificate> certs = new ArrayList<>(); if (certificate != null && certificate.getSize() > 0) { certs.add(createCertificate(certificate, result)); } //Error handling if (result.hasErrors()) { model.addAttribute(application); model.addAttribute("scopes", permissions); return new ModelAndView("application/edit"); } Application savedApp = appManagement.updateApplication(application); //Remove everything old. // Just allow one certificate to be set right now even though the model allows for more. //If this behavior is unwanted the GUI has to adapt for this as well as it only caters //for on certificate right now. if (certificate != null && certificate.getSize() > 0) { Set<Certificate> oldCerts = new HashSet<>(); //Clone in order to not get a concurrent modification exception for (Certificate cert : savedApp.getCertificates()) { oldCerts.add(cert); } //Remove anything old for (Certificate cert : oldCerts) { cert.setApplication(null); savedApp.getCertificates().remove(cert); appManagement.removeCertificate(cert); } //Set the new Certificate for (Certificate cert : certs) { cert.setApplication(savedApp); appManagement.saveCertificate(cert); savedApp.getCertificates().add(cert); } } try { log.debug("Composing message to: {}", mailAddress); MimeMessage message = sender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(message); helper.setTo(mailAddress); helper.setFrom(new InternetAddress(mailFrom)); log.debug("Creating message for existing app. User {} edited the app {}", citizenLoggingUtil.getLogsafeSSN(user.getUsername()), application.getName()); helper.setSubject("Redigerad app: " + application.getName()); helper.setText("Utvecklare med personnr " + user.getUsername() + " har redigerat appen: " + application.getName()); log.debug("Sending mail notification."); sender.send(message); } catch (Exception e) { log.error("Caught exception while trying to send email", e); } } return new ModelAndView("redirect:/developer"); } @RequestMapping(value = "/edit", method = RequestMethod.POST, params = { "edit_icon" }) public ModelAndView editIcon(ModelMap model, Authentication authentication, @ModelAttribute("application") Application application) { UserDetails user = (UserDetails) authentication.getPrincipal(); Developer developer = appManagement.getDeveloper(user.getUsername()); Application savedApp = appManagement.getApplication(application.getId()); //Repopulate the certificates. Maybe there is a better way.. application.setCertificates(savedApp.getCertificates()); if (application == null) { throw new IllegalArgumentException("No application found with"); } if (user.getUsername().equals(developer.getResidentIdentificationNumber())) { List<Permission> permissions = permissionServices.getAllPermissions(); model.addAttribute("editIcon", true); model.addAttribute(application); model.addAttribute("scopes", permissions); return new ModelAndView("application/edit"); } else { throw new IllegalArgumentException("Application developer is not the same as the logged in user"); } } @RequestMapping(value = "/edit", method = RequestMethod.POST, params = { "undo_edit_icon" }) public ModelAndView undoEditIcon(ModelMap model, Authentication authentication, @ModelAttribute("application") Application application) { UserDetails user = (UserDetails) authentication.getPrincipal(); Developer developer = appManagement.getDeveloper(user.getUsername()); Application savedApp = appManagement.getApplication(application.getId()); //Repopulate the certificates. Maybe there is a better way.. application.setCertificates(savedApp.getCertificates()); if (application == null) { throw new IllegalArgumentException("No application found with"); } if (user.getUsername().equals(developer.getResidentIdentificationNumber())) { List<Permission> permissions = permissionServices.getAllPermissions(); model.addAttribute("editIcon", false); model.addAttribute(application); model.addAttribute("scopes", permissions); return new ModelAndView("application/edit"); } else { throw new IllegalArgumentException("Application developer is not the same as the logged in user"); } } @RequestMapping(value = "/edit", method = RequestMethod.POST, params = { "edit_certificate" }) public ModelAndView editCertificate(ModelMap model, Authentication authentication, @ModelAttribute("application") Application application) { UserDetails user = (UserDetails) authentication.getPrincipal(); Developer developer = appManagement.getDeveloper(user.getUsername()); if (application == null) { throw new IllegalArgumentException("No application found with"); } if (user.getUsername().equals(developer.getResidentIdentificationNumber())) { List<Permission> permissions = permissionServices.getAllPermissions(); model.addAttribute("editCertificate", true); model.addAttribute(application); model.addAttribute("scopes", permissions); return new ModelAndView("application/edit"); } else { throw new IllegalArgumentException("Application developer is not the same as the logged in user"); } } @RequestMapping(value = "/edit", method = RequestMethod.POST, params = { "undo_edit_certificate" }) public ModelAndView undoEditCertificate(ModelMap model, Authentication authentication, @ModelAttribute("application") Application application) { UserDetails user = (UserDetails) authentication.getPrincipal(); Developer developer = appManagement.getDeveloper(user.getUsername()); Application savedApp = appManagement.getApplication(application.getId()); //Repopulate the certificates. Maybe there is a better way.. application.setCertificates(savedApp.getCertificates()); if (application == null) { throw new IllegalArgumentException("No application found with"); } if (user.getUsername().equals(developer.getResidentIdentificationNumber())) { List<Permission> permissions = permissionServices.getAllPermissions(); model.addAttribute("editCertificate", false); model.addAttribute(application); model.addAttribute("scopes", permissions); return new ModelAndView("application/edit"); } else { throw new IllegalArgumentException("Application developer is not the same as the logged in user"); } } @RequestMapping(value = "/app", method = RequestMethod.GET, params = { "show" }) public ModelAndView showApp(@RequestParam("show") Long id, ModelMap model, Authentication authentication) { log.debug("Entering Show"); Application application = appManagement.getApplication(id); UserDetails user = (UserDetails) authentication.getPrincipal(); if (!developerExists(user)) { return new ModelAndView("redirect:register_developer"); } if (application == null) { throw new IllegalArgumentException("No application found with id " + id); } if (user.getUsername().equals(application.getDeveloper().getResidentIdentificationNumber())) { List<Permission> permissions = permissionServices.getAllPermissions(); model.addAttribute(application); model.addAttribute("scopes", permissions); return new ModelAndView("application/show"); } else { throw new IllegalArgumentException("Application developer is not the same as the logged in user"); } } @RequestMapping(value = "/app", method = RequestMethod.GET) public ModelAndView registerApp(ModelMap model, Authentication authentication) { UserDetails user = (UserDetails) authentication.getPrincipal(); if (!developerExists(user)) { return new ModelAndView("redirect:register_developer"); } else { return renderAppForm(model, new Application()); } } protected ModelAndView renderAppForm(ModelMap model, Application app) { List<Permission> permissions = permissionServices.getAllPermissions(); //new ArrayList(); // model.addAttribute(app); model.addAttribute("scopes", permissions); return new ModelAndView("register_application"); } @RequestMapping(value = "/app", method = RequestMethod.POST) public String registerApp(ModelMap model, @ModelAttribute("application") Application application, @RequestParam("certificate") MultipartFile certificate, MultipartFile icon, BindingResult result, Authentication authentication) throws IOException { UserDetails user = (UserDetails) authentication.getPrincipal(); Developer developer = appManagement.getDeveloper(user.getUsername()); log.debug("User {} registered or edited the app {}.", citizenLoggingUtil.getLogsafeSSN(user.getUsername()), application.getName()); if (developer == null) { return "redirect:register_developer"; } else { applicationValidator.validate(application, result); application.setRegistrationDate(new Date()); application.setState(State.REGISTRATED); application.setDeveloper(developer); Application existingApp = appManagement.getApplicationByClientId(application.getClientId()); if (existingApp != null) { result.rejectValue("clientId", "invalid.clientId", "Klient id:et finns redan registrerat, var god vlj ett annat."); } // validate icon if it exists if (application.getIcon() != null && application.getIcon().length > 0) { try { ByteArrayInputStream bis = new ByteArrayInputStream(application.getIcon()); BufferedImage bufferedImage = ImageIO.read(bis); log.info("Width: " + bufferedImage.getWidth() + " Height: " + bufferedImage.getHeight()); application.setIconContentType(icon.getContentType()); // TODO: Check width and height here! } catch (Exception e) { result.rejectValue("icon", "invalid.icon", "Ikonen r ej giltig"); } } //For now we are just allowing the addition of just one certificate List<Certificate> certs = new ArrayList<>(); if (certificate != null && certificate.getSize() > 0) { certs.add(createCertificate(certificate, result)); } if (result.hasErrors()) { List<Permission> permissions = permissionServices.getAllPermissions(); model.addAttribute("scopes", permissions); return "register_application"; } Application savedApp = appManagement.registerApplication(application); for (Certificate cert : certs) { cert.setApplication(savedApp); appManagement.saveCertificate(cert); savedApp.getCertificates().add(cert); } try { log.debug("Composing message to: {}", mailAddress); MimeMessage message = sender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(message); helper.setTo(mailAddress); helper.setFrom(new InternetAddress(mailFrom)); log.debug("Creating message for existing app. User {} edited the app {}", citizenLoggingUtil.getLogsafeSSN(user.getUsername()), application.getName()); helper.setSubject("Redigerad app: " + application.getName()); helper.setText("Utvecklare med personnr " + user.getUsername() + " har redigerat appen: " + application.getName()); log.debug("Sending mail notification."); sender.send(message); } catch (Exception e) { log.error("Caught exception while trying to send email", e); } return "redirect:"; } } private Certificate createCertificate(MultipartFile certificate, BindingResult result) { Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); Certificate cert = new Certificate(); if (certificate != null && certificate.getSize() > 0) { try { PEMReader r = new PEMReader( new InputStreamReader(new ByteArrayInputStream(certificate.getBytes()))); Object certObj = r.readObject(); long reference = System.currentTimeMillis(); // validate certificate if (certObj instanceof X509Certificate) { X509Certificate x509cert = (X509Certificate) certObj; BigInteger serialNumber = x509cert.getSerialNumber(); String issuerDn = x509cert.getIssuerDN().getName(); String subjectDn = x509cert.getSubjectDN().getName(); cert.setCertificate(certificate.getBytes()); cert.setSerialNumber(serialNumber.toString()); cert.setIssuer(issuerDn); cert.setSubject(subjectDn); cert.setSubjectCommonName(extractFromDn(subjectDn, "CN")); cert.setSubjectOrganization(extractFromDn(subjectDn, "O")); cert.setSubjectOrganizationUnit(extractFromDn(subjectDn, "OU")); cert.setSubjectLocation(extractFromDn(subjectDn, "L")); cert.setSubjectCountry(extractFromDn(subjectDn, "C")); cert.setNotAfter(x509cert.getNotAfter()); cert.setNotBefore(x509cert.getNotBefore()); } else { String line; StringBuilder certString = new StringBuilder(); while ((line = r.readLine()) != null) { certString.append(line + "\n"); } log.warn( "Bad certificate [{}]: Provided certificate was of the wrong type: {}. Certificate: \n{}", new Object[] { reference, certObj, certString.toString() }); result.rejectValue("certificates", "invalid.certificate", "Certifikatet r ej giltigt (Reference: " + reference + ")"); } r.close(); } catch (IOException e) { log.warn("Bad certificate"); result.rejectValue("certificates", "invalid.certificate", "Certifikatet r ej giltigt "); } } return cert; } protected String extractFromDn(String dn, String unit) { Pattern subjectDnPattern = Pattern.compile(unit + "=([^,]*)", Pattern.CASE_INSENSITIVE); Matcher matcher = subjectDnPattern.matcher(dn); if (matcher.find()) { return matcher.group(1); } else { return null; } } @RequestMapping(value = "/app", params = { "delete" }) public ModelAndView deleteApp(@RequestParam("delete") Long id, Authentication authentication) { UserDetails user = (UserDetails) authentication.getPrincipal(); Developer developer = appManagement.getDeveloper(user.getUsername()); log.debug("User {} deleted the app with id {}.", citizenLoggingUtil.getLogsafeSSN(user.getUsername()), id); if (developer == null) { return new ModelAndView("redirect:register_developer"); } else { Application application = appManagement.getApplication(id); log.debug("Deleting the app {}.", application.getName()); if (application.getDeveloper().getResidentIdentificationNumber().equals(user.getUsername())) { appManagement.removeApplication(id); try { MimeMessage message = sender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(message); helper.setTo(mailAddress); helper.setFrom(new InternetAddress(mailFrom)); helper.setSubject("Borttagen app: " + application.getName()); helper.setText("Utvecklare med personnr " + user.getUsername() + " har tagit bort appen: " + application.getName()); sender.send(message); } catch (Exception e) { log.error("Caught exception while trying to send email", e); } } else { throw new RuntimeException( "The logged in user is not the same as the developer of the application!"); } return new ModelAndView("redirect:"); } } private boolean developerExists(UserDetails user) { Developer developer = appManagement.getDeveloper(user.getUsername()); return developer != null; } @RequestMapping(value = "/app/image", method = RequestMethod.GET, params = { "id" }) public ResponseEntity<byte[]> getIcon(@RequestParam("id") Long id) { Application application = appManagement.getApplication(id); if (application == null) { throw new IllegalArgumentException("No application found with id " + id); } byte[] content = application.getIcon(); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.IMAGE_JPEG); return new ResponseEntity<byte[]>(content, headers, HttpStatus.OK); } }