Java tutorial
/* * WebTop Services is a Web Application framework developed by Sonicle S.r.l. * Copyright (C) 2014 Sonicle S.r.l. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by * the Free Software Foundation with the addition of the following permission * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED * WORK IN WHICH THE COPYRIGHT IS OWNED BY SONICLE, SONICLE DISCLAIMS THE * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * This program 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 General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License * along with this program; if not, see http://www.gnu.org/licenses or write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA. * * You can contact Sonicle S.r.l. at email address sonicle@sonicle.com * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License * version 3, these Appropriate Legal Notices must retain the display of the * Sonicle logo and Sonicle copyright notice. If the display of the logo is not * reasonably feasible for technical reasons, the Appropriate Legal Notices must * display the words "Copyright (C) 2014 Sonicle S.r.l.". */ package com.sonicle.webtop.core.app; import com.sonicle.commons.EnumUtils; import com.sonicle.commons.InternetAddressUtils; import com.sonicle.commons.LangUtils; import com.sonicle.commons.URIUtils; import com.sonicle.commons.net.IPUtils; import com.sonicle.commons.web.json.JsonResult; import com.sonicle.commons.web.ServletUtils; import com.sonicle.security.DomainAccount; import com.sonicle.security.Principal; import com.sonicle.security.otp.OTPKey; import com.sonicle.security.otp.OTPProviderFactory; import com.sonicle.security.otp.provider.GoogleAuth; import com.sonicle.security.otp.provider.GoogleAuthOTPKey; import com.sonicle.security.otp.provider.SonicleAuth; import com.sonicle.webtop.core.CoreLocaleKey; import com.sonicle.webtop.core.CoreServiceSettings; import com.sonicle.webtop.core.CoreSettings; import com.sonicle.webtop.core.CoreSettings.OtpDeliveryMode; import com.sonicle.webtop.core.CoreUserSettings; import com.sonicle.webtop.core.TplHelper; import com.sonicle.webtop.core.bol.ODomain; import com.sonicle.webtop.core.bol.OUserSetting; import com.sonicle.webtop.core.bol.js.JsTrustedDevice; import com.sonicle.webtop.core.bol.js.TrustedDeviceCookie; import com.sonicle.webtop.core.sdk.UserProfile; import com.sonicle.webtop.core.sdk.UserProfileId; import com.sonicle.webtop.core.sdk.WTException; import com.sonicle.webtop.core.util.NotificationHelper; import freemarker.template.TemplateException; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.UUID; import java.util.concurrent.TimeUnit; import javax.mail.MessagingException; import javax.mail.internet.InternetAddress; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import net.glxn.qrgen.javase.QRCode; import org.apache.commons.codec.digest.DigestUtils; import org.slf4j.Logger; /** * * @author malbinola */ public class OTPManager { private static final Logger logger = WT.getLogger(OTPManager.class); private static boolean initialized = false; /** * Initialization method. This method should be called once. * * @param wta WebTopApp instance. * @return The instance. */ static synchronized OTPManager initialize(WebTopApp wta) { if (initialized) throw new RuntimeException("Initialization already done"); OTPManager otpm = new OTPManager(wta); initialized = true; logger.info("Initialized"); return otpm; } private WebTopApp wta = null; /** * Private constructor. * Instances of this class must be created using static initialize method. * @param wta WebTopApp instance. */ private OTPManager(WebTopApp wta) { this.wta = wta; } /** * Performs cleanup process. */ void cleanup() { wta = null; logger.info("Cleaned up"); } public boolean isEnabled(UserProfileId pid) { CoreUserSettings cus = new CoreUserSettings(pid); return (cus.getOTPDelivery() == null) ? false : cus.getOTPEnabled(); //return StringUtils.isBlank(cus.getOTPDelivery()) ? false : cus.getOTPEnabled(); } public OtpDeliveryMode getDeliveryMode(UserProfileId pid) { CoreUserSettings cus = new CoreUserSettings(pid); return cus.getOTPDelivery(); } public String getEmailAddress(UserProfileId pid) { CoreUserSettings cus = new CoreUserSettings(pid); return cus.getOTPEmailAddress(); } private String getSecret(UserProfileId pid) { CoreUserSettings cus = new CoreUserSettings(pid); return cus.getOTPSecret(); } public void deactivate(UserProfileId pid) { CoreUserSettings cus = new CoreUserSettings(pid); cus.clear(CoreSettings.OTP_SECRET); cus.clear(CoreSettings.OTP_EMAILADDRESS); cus.clear(CoreSettings.OTP_DELIVERY); cus.setOTPEnabled(false); } public EmailConfig configureEmail(UserProfileId pid, String emailAddress) throws WTException { SonicleAuth sa = (SonicleAuth) OTPProviderFactory.getInstance("SonicleAuth"); UserProfile.Data ud = wta.getWebTopManager().userData(pid); InternetAddress to = InternetAddressUtils.toInternetAddress(emailAddress); if (to == null) throw new WTException("Invalid destination address [{0}]", emailAddress); OTPKey otp = sa.generateCredentials(ud.getEmail().getAddress()); sendCodeEmail(pid, ud.getLocale(), to, otp.getVerificationCode()); return new EmailConfig(otp, emailAddress); } public GoogleAuthConfig configureGoogleAuth(UserProfileId pid, int qrCodeSize) throws WTException { GoogleAuth ga = (GoogleAuth) OTPProviderFactory.getInstance("GoogleAuth"); OTPKey otp = ga.generateCredentials(); byte[] qrcode = generateGoogleAuthQRCode(pid, otp, qrCodeSize); return new GoogleAuthConfig(otp, qrcode); } public boolean activate(UserProfileId pid, Config config, int code) throws WTException { CoreUserSettings cus = new CoreUserSettings(pid); if (config instanceof EmailConfig) { SonicleAuth te = (SonicleAuth) OTPProviderFactory.getInstance("SonicleAuth"); CoreServiceSettings css = new CoreServiceSettings(CoreManifest.ID, pid.getDomainId()); long interval = css.getOTPProviderSonicleAuthKVI(); if (te.check(code, config.otp.getVerificationCode(), Long.valueOf(config.otp.getKey()), interval)) { cus.setOTPEmailAddress(((EmailConfig) config).emailAddress); cus.setOTPDelivery(OtpDeliveryMode.EMAIL); cus.setOTPEnabled(true); return true; } else { return false; } } else if (config instanceof GoogleAuthConfig) { GoogleAuth ga = (GoogleAuth) OTPProviderFactory.getInstance("GoogleAuth"); if (ga.check(code, config.otp.getKey())) { cus.setOTPSecret(config.otp.getKey()); cus.setOTPDelivery(OtpDeliveryMode.GOOGLEAUTH); cus.setOTPEnabled(true); return true; } else { return false; } } return false; } private byte[] generateGoogleAuthQRCode(UserProfileId pid, OTPKey otp, int size) throws WTException { ODomain domain = wta.getWebTopManager().getDomain(pid.getDomainId()); if (domain == null) throw new WTException("Domain not found [{0}]", pid.getDomainId()); String issuer = URIUtils .encodeQuietly(MessageFormat.format("{0} ({1})", WT.getPlatformName(), domain.getInternetName())); InternetAddress ia = InternetAddressUtils.toInternetAddress(pid.getUserId(), domain.getInternetName(), null); if (ia == null) throw new WTException("Unable to build account address"); String uri = GoogleAuthOTPKey.buildAuthenticatorURI(issuer, otp.getKey(), ia.getAddress()); logger.debug("Generating OPT QRCode for {}", uri); return QRCode.from(uri).withSize(size, size).stream().toByteArray(); } public Config prepareCheckCode(UserProfileId pid) throws WTException { OtpDeliveryMode deliveryMode = getDeliveryMode(pid); if (OtpDeliveryMode.EMAIL.equals(deliveryMode)) { SonicleAuth te = (SonicleAuth) OTPProviderFactory.getInstance("SonicleAuth"); UserProfile.Data ud = wta.getWebTopManager().userData(pid); String emailAddress = getEmailAddress(pid); InternetAddress to = InternetAddressUtils.toInternetAddress(emailAddress); if (to == null) throw new WTException("Invalid destination address [{0}]", emailAddress); OTPKey otp = te.generateCredentials(ud.getEmail().getAddress()); sendCodeEmail(pid, ud.getLocale(), to, otp.getVerificationCode()); return new Config(EnumUtils.toSerializedName(OtpDeliveryMode.EMAIL), otp); } else { return new Config(EnumUtils.toSerializedName(OtpDeliveryMode.GOOGLEAUTH), null); } } public boolean checkCode(UserProfileId pid, Config data, int code) { OtpDeliveryMode deliveryMode = getDeliveryMode(pid); if (OtpDeliveryMode.EMAIL.equals(deliveryMode)) { CoreServiceSettings css = new CoreServiceSettings(CoreManifest.ID, pid.getDomainId()); SonicleAuth te = (SonicleAuth) OTPProviderFactory.getInstance("SonicleAuth"); long interval = css.getOTPProviderSonicleAuthKVI(); return te.check(code, data.otp.getVerificationCode(), Long.valueOf(data.otp.getKey()), interval); } else { GoogleAuth ga = (GoogleAuth) OTPProviderFactory.getInstance("GoogleAuth"); return ga.check(code, getSecret(pid)); } } public void registerTrustedDevice(UserProfileId pid, JsTrustedDevice td) { SettingsManager sm = wta.getSettingsManager(); String key = CoreSettings.OTP_TRUSTED_DEVICE + "@" + td.deviceId; sm.setUserSetting(pid.getDomainId(), pid.getUserId(), CoreManifest.ID, key, JsonResult.gson.toJson(td)); } public boolean removeTrustedDevice(UserProfileId pid, String deviceId) { SettingsManager sm = wta.getSettingsManager(); String key = CoreSettings.OTP_TRUSTED_DEVICE + "@" + deviceId; return sm.deleteUserSetting(pid.getDomainId(), pid.getUserId(), CoreManifest.ID, key); } public JsTrustedDevice getTrustedDevice(UserProfileId pid, String deviceId) { SettingsManager sm = wta.getSettingsManager(); String key = CoreSettings.OTP_TRUSTED_DEVICE + "@" + deviceId; return LangUtils.value(sm.getUserSetting(pid.getDomainId(), pid.getUserId(), CoreManifest.ID, key), null, JsTrustedDevice.class); } public ArrayList<JsTrustedDevice> listTrustedDevices(UserProfileId pid) { SettingsManager sm = wta.getSettingsManager(); List<OUserSetting> items = sm.getUserSettings(pid.getDomainId(), pid.getUserId(), CoreManifest.ID, CoreSettings.OTP_TRUSTED_DEVICE + "%"); return JsTrustedDevice.asList(items); } public JsTrustedDevice trustThisDevice(UserProfileId pid, String userAgentHeader) { String deviceId = DigestUtils.shaHex(UUID.randomUUID().toString() + userAgentHeader); long now = new Date().getTime(); JsTrustedDevice td = new JsTrustedDevice(deviceId, DomainAccount.buildName(pid.getDomainId(), pid.getUserId()), now, userAgentHeader); registerTrustedDevice(pid, td); return td; } public boolean isThisDeviceTrusted(UserProfileId pid, TrustedDeviceCookie tdc) { if (tdc == null) return false; CoreServiceSettings css = new CoreServiceSettings(CoreManifest.ID, pid.getDomainId()); // Checks (if enabled) cookie duration int duration = css.getOTPDeviceTrustDuration(); if (duration > 0) { long now = new Date().getTime(); long expires = tdc.timestamp + TimeUnit.DAYS.toMillis(duration); if (now > expires) { logger.trace("Device cookie expired [{}days, {} > {}]", duration, now, expires); return false; } } // Checks if device is registered JsTrustedDevice td = getTrustedDevice(pid, tdc.deviceId); if (td == null) { logger.trace("Device ID not registered before [{}]", tdc.deviceId); return false; } // Checks account match if (!td.account.equals(tdc.account)) { logger.trace("Device ID not bound to the right account [{} != {}]", tdc.account, td.account); return false; } return true; } public boolean isTrusted(UserProfileId pid, String remoteIP) { CoreServiceSettings css = new CoreServiceSettings(CoreManifest.ID, pid.getDomainId()); String addresses = css.getOTPTrustedAddresses(); if (addresses != null) { String[] cidrs = SettingsManager.asArray(addresses); try { boolean inRange = IPUtils.isIPInRange(cidrs, remoteIP); if (inRange) return true; } catch (Exception ex) { logger.error("Problem performing IP range check", ex); } } return false; } public void clearTrustedDeviceCookie(UserProfileId pid, HttpServletResponse response) { String name = MessageFormat.format("TD_{0}", Principal.buildHashedName(pid.getDomainId(), pid.getUserId())); ServletUtils.eraseCookie(response, name); } public TrustedDeviceCookie readTrustedDeviceCookie(UserProfileId pid, HttpServletRequest request) { String secret = getSecret(pid); String name = MessageFormat.format("TD_{0}", Principal.buildHashedName(pid.getDomainId(), pid.getUserId())); return ServletUtils.getEncryptedCookie(secret, request, name, TrustedDeviceCookie.class); } public void writeTrustedDeviceCookie(UserProfileId pid, HttpServletResponse response, TrustedDeviceCookie tdc) { String secret = getSecret(pid); String name = MessageFormat.format("TD_{0}", Principal.buildHashedName(pid.getDomainId(), pid.getUserId())); int duration = 60 * 60 * 24 * 365 * 2; // 2 years ServletUtils.setEncryptedCookie(secret, response, name, tdc, TrustedDeviceCookie.class, duration); } private void sendCodeEmail(UserProfileId pid, Locale locale, InternetAddress to, int code) throws WTException { try { String bodyHeader = WT.lookupResource(CoreManifest.ID, locale, CoreLocaleKey.TPL_EMAIL_OTPCODEVERIFICATION_BODY_HEADER); String subject = NotificationHelper.buildSubject(locale, CoreManifest.ID, bodyHeader); String html = TplHelper.buildOtpCodeVerificationEmail(locale, String.valueOf(code)); InternetAddress from = WT.getNoReplyAddress(pid.getDomainId()); if (from == null) throw new WTException("Error building sender address"); WT.sendEmail(WT.getGlobalMailSession(pid), true, from, to, subject, html); } catch (IOException | TemplateException ex) { logger.error("Unable to build email template", ex); } catch (MessagingException ex) { logger.error("Unable to send email", ex); } } public static class Config { public String delivery; public OTPKey otp; private Config(String delivery, OTPKey otp) { this.delivery = delivery; this.otp = otp; } } public static class EmailConfig extends Config { public String emailAddress; private EmailConfig(OTPKey otp, String emailAddress) { super(EnumUtils.toSerializedName(OtpDeliveryMode.EMAIL), otp); this.emailAddress = emailAddress; } } public static class GoogleAuthConfig extends Config { public byte[] qrcode; private GoogleAuthConfig(OTPKey otp, byte[] qrcode) { super(EnumUtils.toSerializedName(OtpDeliveryMode.GOOGLEAUTH), otp); this.qrcode = qrcode; } } }