/** * Copyright (c) 2011, SOCIETIES Consortium (WATERFORD INSTITUTE OF TECHNOLOGY (TSSG), HERIOT-WATT UNIVERSITY (HWU), SOLUTA.NET * (SN), GERMAN AEROSPACE CENTRE (Deutsches Zentrum fuer Luft- und Raumfahrt e.V.) (DLR), Zavod za varnostne tehnologije * informacijske družbe in elektronsko poslovanje (SETCCE), INSTITUTE OF COMMUNICATION AND COMPUTER SYSTEMS (ICCS), LAKE * COMMUNICATIONS (LAKE), INTEL PERFORMANCE LEARNING SOLUTIONS LTD (INTEL), PORTUGAL TELECOM INOVAÇÃO, SA (PTIN), IBM Corp., * INSTITUT TELECOM (ITSUD), AMITEC DIACHYTI EFYIA PLIROFORIKI KAI EPIKINONIES ETERIA PERIORISMENIS EFTHINIS (AMITEC), TELECOM * ITALIA S.p.a.(TI), TRIALOG (TRIALOG), Stiftelsen SINTEF (SINTEF), NEC EUROPE LTD (NEC)) * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following * conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.societies.domainauthority.webapp.controller; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.StringReader; import java.net.URL; import java.net.URLConnection; import java.net.URLEncoder; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.servlet.ServletContext; import javax.validation.Valid; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import net.glxn.qrgen.QRCode; import net.glxn.qrgen.image.ImageType; import org.apache.commons.codec.binary.Base64; import org.societies.api.comm.xmpp.interfaces.ICommManager; import org.societies.api.css.directory.ICssDirectory; import org.societies.api.schema.css.directory.CssAdvertisementRecord; import org.societies.domainauthority.registry.DaRegistry; import org.societies.domainauthority.registry.DaUserRecord; import org.societies.domainauthority.webapp.models.LoginForm; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.ModelAndView; import org.w3c.dom.Document; import org.xml.sax.InputSource; @Controller public class IndexController { private static final String OPENFIRE_PLUGIN = "http://%s:9090/plugins/societies/societies"; private Map<String, String> domains = new LinkedHashMap<String, String>(); private String xmppDomain; @Autowired private ICommManager commManager; @Autowired DaRegistry daRegistry; @Autowired ICssDirectory cssDir; @Autowired ServletContext context; public IndexController() { } @InitBinder public void init() { xmppDomain = commManager.getIdManager().getThisNetworkNode().getDomain(); domains.put(xmppDomain, xmppDomain); } @RequestMapping(value = "/index.html", method = RequestMethod.GET) public ModelAndView signInInit() { Map<String, Object> model = new HashMap<String, Object>(); LoginForm loginForm = new LoginForm(); loginForm.setMethod("login"); model.put("loginForm", loginForm); model.put("domains", domains); return new ModelAndView("index", model); } @SuppressWarnings({ "unchecked", "rawtypes" }) @RequestMapping(value = "/index.html", method = RequestMethod.POST) public ModelAndView processSignIn(@Valid LoginForm loginForm, BindingResult result, Map model) { model.put("domains", domains); // -- Retrieve params String userName = loginForm.getUserName(); String password = loginForm.getPassword(); String subDomain = loginForm.getSubDomain(); Map<String, String> currentValues = new LinkedHashMap<String, String>(); currentValues.put("subDomain", subDomain); currentValues.put("username", userName); currentValues.put("password", password); // -- Check params // Error if (result.hasErrors()) { model.put("errormsg", "Some errors occured."); model.putAll(currentValues); return new ModelAndView("signup", model); } // Empty params if (null == userName || "".equals(userName) || password == null || "".equals(password) || subDomain == null || "".equals(subDomain)) { model.put("errormsg", "Some required information are missing."); model.putAll(currentValues); return new ModelAndView("signup", model); } // -- AUTHENTICATE JABBER ID Map<String, String> params = new LinkedHashMap<String, String>(); params.put("username", userName); params.put("password", password); params.put("subDomain", subDomain); params.put("secret", "defaultSecret"); String xmppUrl = new String(); xmppUrl = String.format(OPENFIRE_PLUGIN, subDomain); String resp = postData(MethodType.LOGIN, xmppUrl, params); try { // Error: empty result if (resp.isEmpty()) { model.put("errormsg", "Error logging onto SOCIETIES account. Please try again."); return new ModelAndView("index", model); } // Result: but error in result Document respDoc = loadXMLFromString(resp); if (respDoc.getDocumentElement().getNodeName().equals("error")) { model.put("errormsg", "Username or password incorrect, please try again."); return new ModelAndView("index", model); } } catch (Exception e) { e.printStackTrace(); model.put("errormsg", "Error logging onto SOCIETIES account. Please try again."); return new ModelAndView("index", model); } // -- GET CONTAINER INFO DaUserRecord userRecord = new DaUserRecord(); userRecord = daRegistry.getXmppIdentityDetails(userName); if ((userRecord != null) && (userRecord.getStatus() != null)) { if (!userRecord.getStatus().contentEquals("active") || userRecord.getHost().isEmpty() || userRecord.getPort().isEmpty()) { //CLOUD NODE NOT SETUP YET model.put("errormsg", "Account creation not completed. Contact a SOCIETIES administrator to activate your account."); return new ModelAndView("index", model); } } else { // account doesn't exist //CLOUD NODE NOT SETUP YET model.put("errormsg", "User Acount Not Found"); return new ModelAndView("index", model); } // Login and redirect to User Webapp String serializedPassword = toBytesString(password); String redirectUrlIndex = "http://"+userRecord.getHost()+":"+userRecord.getPort()+"/societies/index.xhtml"; String redirectUrlLogin = "http://"+userRecord.getHost()+":"+userRecord.getPort()+"/societies/rest_login.xhtml?username="+userName+"&passworddigest="+serializedPassword+"&redirect=true"; // model.put("debugmsg", "Request:"+redirectUrlLogin+", Response:"+loginToUserWebapp(userRecord.getHost(), userRecord.getPort(), userName, password, false)+", Unserialized password:"+fromBytesString(serializedPassword)); if (loginToUserWebapp(userRecord.getHost(), userRecord.getPort(), userName, password, false)) { return new ModelAndView("redirect:"+redirectUrlLogin); } model.put("errormsg", "Your are authenticated by the SOCIETIES Domain Authority. Unfortunately, sign in failed on your SOCIETIES webapp. Please, <a href=\""+redirectUrlIndex+"\">go to your SOCIETIES webapp</a> and sign in manually, or contact a SOCIETIES administrator."); return new ModelAndView("index", model); } private boolean loginToUserWebapp(String hostname, String port, String username, String password, boolean redirect) { StringBuffer data = new StringBuffer(); StringBuffer sb = new StringBuffer(); String stringUrl = null; try { // NOTE: serializing the password is not sufficient. But the whole login system has to be refactored due to high security issues. String serializedPassword = toBytesString(password); stringUrl = "http://"+hostname+":"+port+"/societies/rest_login.xhtml?username="+username+"&passworddigest="+serializedPassword+"&redirect="+(redirect ? "true" : "false"); URL url = new URL(stringUrl.trim()); URLConnection conn = url.openConnection(); conn.setDoOutput(true); OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream()); wr.write(data.toString()); wr.flush(); // Get the response BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream())); String line; while ((line = rd.readLine()) != null) { sb.append(line); } wr.close(); rd.close(); } catch(Exception e) { e.printStackTrace(); return false; } return "200".equals(sb.toString().trim()); } private String toBytesString(String str) { StringBuilder sb = new StringBuilder(); byte[] bytes = str.getBytes(); for (int i=0; i<bytes.length; i++) { sb.append(bytes[i]); if ((i+1)!=bytes.length) { sb.append(":"); } } return sb.toString(); } private String fromBytesString(String bytesStr) { String[] byteValues = bytesStr.split(":"); byte[] bytes = new byte[byteValues.length]; for (int i=0, len=bytes.length; i<len; i++) { bytes[i] = Byte.valueOf(byteValues[i].trim()); } return new String(bytes); } @RequestMapping(value = "/signup.html", method = RequestMethod.GET) public ModelAndView signupInit() { Map<String, Object> model = new HashMap<String, Object>(); LoginForm signupForm = new LoginForm(); model.put("loginForm", signupForm); model.put("domains", domains); return new ModelAndView("signup", model); } @SuppressWarnings({ "unchecked", "rawtypes" }) @RequestMapping(value = "/signup.html", method = RequestMethod.POST) public ModelAndView processSignup(@Valid LoginForm signupForm, BindingResult result, Map model) { model.put("domains", domains); // -- Retrieve params String userName = signupForm.getUserName(); String password = signupForm.getPassword(); String passwordConfirmation = signupForm.getPasswordConfirm(); String Name = signupForm.getName(); String subDomain = signupForm.getSubDomain(); Map<String, String> currentValues = new LinkedHashMap<String, String>(); currentValues.put("subDomain", subDomain); currentValues.put("name", Name); currentValues.put("username", userName); currentValues.put("password", password); currentValues.put("passwordConfirm", passwordConfirmation); // -- Check new account params // Error if (result.hasErrors()) { model.put("errormsg", "Some errors occured."); model.putAll(currentValues); return new ModelAndView("signup", model); } // Empty params if (null == userName || "".equals(userName) || password == null || "".equals(password) || subDomain == null || "".equals(subDomain)) { model.put("errormsg", "Some required information are missing."); model.putAll(currentValues); return new ModelAndView("signup", model); } // Password if ((password.contentEquals(passwordConfirmation)) == false) { //account doesn't exist, direct to new user page model.put("errormsg", "The password confirmation is different from the choosen password."); model.putAll(currentValues); return new ModelAndView("signup", model); } // Already exists String newUserUri = userName+"."+subDomain; try { List<CssAdvertisementRecord> existingAccounts = cssDir.findAllCssAdvertisementRecords().get(); System.out.println("### Existing accounts: "+existingAccounts); if (null != existingAccounts && existingAccounts.size() > 0) { for(CssAdvertisementRecord record : existingAccounts) { if (newUserUri.equals(record.getUri())) { model.put("errormsg", "A such username already exists. Please select an other one."); model.putAll(currentValues); return new ModelAndView("signup", model); } } } } catch (Exception e) { // too bad... Let's continue to create the account, if it already exists the user will think it has created an account but it has not. e.printStackTrace(); } // -- Create account // Create Openfire Account Map<String, String> params = new LinkedHashMap<String, String>(); params.put("username", userName); params.put("password", password); params.put("name", Name); params.put("secret", "defaultSecret"); String xmppUrl = new String(); xmppUrl = String.format(OPENFIRE_PLUGIN, xmppDomain); String resp = postData(MethodType.ADD, xmppUrl, params); try { // CHECK RESPONSE - DOES ACCOUNT ALREADY EXIST Document respDoc = loadXMLFromString(resp); if (respDoc.getDocumentElement().getNodeName().equals("error")) { System.out.println("IndexController::processLogin: can't the account on Openfire."); model.put("infomsg", "The account has not been created on Openfire. It may already exist."); // return new ModelAndView("index", model); } } catch (Exception e) { e.printStackTrace(); } // Create account in da node String jid = userName + "." + subDomain; DaUserRecord userRecord = new DaUserRecord(); userRecord.setName(userName); userRecord.setId(jid); userRecord.setHost(""); userRecord.setPort(""); userRecord.setStatus("new"); userRecord.setUserType("user"); userRecord.setPassword(password); daRegistry.addXmppIdentityDetails(userRecord); CssAdvertisementRecord cssAdvert = new CssAdvertisementRecord(); cssAdvert.setId(jid); cssAdvert.setUri(jid); cssAdvert.setName(Name); cssDir.addCssAdvertisementRecord(cssAdvert); model.put("result", "Account Created - Your cloud node will be created by a SOCIETIES administrator in next 24 hours."); return new ModelAndView("signup", model); } @RequestMapping(value = "/download.html") public ModelAndView downloadInit() { Map<String, Object> model = new HashMap<String, Object>(); // Retrieve current URL DaUserRecord adminUserRecord = new DaUserRecord(commManager.getIdManager().getThisNetworkNode().getIdentifier(), commManager.getIdManager().getThisNetworkNode().getIdentifier()+commManager.getIdManager().getThisNetworkNode().getDomain(), commManager.getIdManager().getThisNetworkNode().getDomain(), "50000", "active", "admin", "defaultpassword"); List<DaUserRecord> userRecords = daRegistry.getXmppIdentityDetails(); for(Iterator<DaUserRecord> it = userRecords.iterator(); it.hasNext();) { DaUserRecord current = it.next(); if ("admin".equals(current.getUserType())) { adminUserRecord = current; break; } } // APK access String downloadPath = context.getContextPath()+"/download/"; String downloadUrl = "http://"+adminUserRecord.getHost()+":"+adminUserRecord.getPort()+downloadPath; String societiesAndroidCommsAppFilename = "SocietiesAndroidCommsApp.apk"; String societiesAndroidCommsAppPath = downloadPath+societiesAndroidCommsAppFilename; String societiesAndroidCommsAppUrl = downloadUrl+societiesAndroidCommsAppFilename; String societiesAndroidAppFilename = "SocietiesAndroidApp.apk"; String societiesAndroidAppUrl = downloadUrl+societiesAndroidAppFilename; String societiesAndroidAppPath = downloadPath+societiesAndroidAppFilename; model.put("societiesAndroidCommsAppQrCodePath", getQrCodeDataUri(societiesAndroidCommsAppUrl)); model.put("societiesAndroidCommsAppPath", societiesAndroidCommsAppPath); model.put("societiesAndroidAppQrCodePath", getQrCodeDataUri(societiesAndroidAppUrl)); model.put("societiesAndroidAppPath", societiesAndroidAppPath); return new ModelAndView("download", model); } private String getQrCodeDataUri(String data) { ByteArrayOutputStream qrCodeData = QRCode.from(data).to(ImageType.PNG).withSize(250, 250).stream(); return "data:image/png;base64,"+new String(Base64.encodeBase64(qrCodeData.toByteArray())); } private static String postData(MethodType method, String openfireUrl, Map<String, String> params) { try { StringBuffer data = new StringBuffer(); for(String s : params.keySet()) { String tmp = URLEncoder.encode(s, "UTF-8") + "=" + URLEncoder.encode((String)params.get(s), "UTF-8") + "&"; data.append(tmp); } //ADD METHOD String methodStr = URLEncoder.encode("type", "UTF-8") + "=" + URLEncoder.encode(method.toString().toLowerCase(), "UTF-8"); data.append(methodStr); // Send data URL url = new URL(openfireUrl); URLConnection conn = url.openConnection(); conn.setDoOutput(true); OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream()); wr.write(data.toString()); wr.flush(); // Get the response BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream())); StringBuffer sb = new StringBuffer(); String line; while ((line = rd.readLine()) != null) { sb.append(line); } wr.close(); rd.close(); //RESPONSE CODE return sb.toString(); } catch (Exception e) { e.printStackTrace(); } return ""; } private Document loadXMLFromString(String xml) throws Exception{ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); InputSource is = new InputSource(new StringReader(xml)); return builder.parse(is); } private enum MethodType { ADD, DELETE, ENABLE, DISABLE, UPDATE, LOGIN; } public ICssDirectory getCssDir() { return cssDir; } public void setCssDir(ICssDirectory cssDir) { this.cssDir = cssDir; } public ICommManager getCommManager() { return commManager; } public void setCommManager(ICommManager commManager) { this.commManager = commManager; } }