|
| 1 | +package it.eng.spagobi.commons.services; |
| 2 | + |
| 3 | +import java.awt.image.BufferedImage; |
| 4 | +import java.io.ByteArrayOutputStream; |
| 5 | +import java.net.URL; |
| 6 | +import java.nio.charset.StandardCharsets; |
| 7 | +import java.util.Base64; |
| 8 | +import java.util.HashMap; |
| 9 | +import java.util.HashSet; |
| 10 | +import java.util.Map; |
| 11 | +import java.util.Set; |
| 12 | + |
| 13 | +import javax.imageio.ImageIO; |
| 14 | +import javax.servlet.http.HttpServletRequest; |
| 15 | +import javax.ws.rs.GET; |
| 16 | +import javax.ws.rs.POST; |
| 17 | +import javax.ws.rs.Path; |
| 18 | +import javax.ws.rs.Produces; |
| 19 | +import javax.ws.rs.core.Context; |
| 20 | +import javax.ws.rs.core.MediaType; |
| 21 | +import javax.ws.rs.core.Response; |
| 22 | + |
| 23 | +import org.apache.commons.lang3.StringUtils; |
| 24 | +import org.apache.log4j.Logger; |
| 25 | + |
| 26 | +import com.auth0.jwt.exceptions.TokenExpiredException; |
| 27 | +import com.google.common.io.Resources; |
| 28 | + |
| 29 | +import it.eng.knowage.mailsender.IMailSender; |
| 30 | +import it.eng.knowage.mailsender.dto.MessageMailDto; |
| 31 | +import it.eng.knowage.mailsender.dto.ProfileNameMailEnum; |
| 32 | +import it.eng.knowage.mailsender.dto.TypeMailEnum; |
| 33 | +import it.eng.knowage.mailsender.factory.FactoryMailSender; |
| 34 | +import it.eng.spago.error.EMFUserError; |
| 35 | +import it.eng.spagobi.commons.SingletonConfig; |
| 36 | +import it.eng.spagobi.commons.bo.Role; |
| 37 | +import it.eng.spagobi.commons.dao.DAOFactory; |
| 38 | +import it.eng.spagobi.commons.dao.IRoleDAO; |
| 39 | +import it.eng.spagobi.commons.metadata.SbiCommonInfo; |
| 40 | +import it.eng.spagobi.commons.metadata.SbiExtRoles; |
| 41 | +import it.eng.spagobi.commons.validation.PasswordChecker; |
| 42 | +import it.eng.spagobi.profiling.bean.SbiUser; |
| 43 | +import it.eng.spagobi.profiling.bean.SbiUserAttributes; |
| 44 | +import it.eng.spagobi.profiling.bean.SbiUserAttributesId; |
| 45 | +import it.eng.spagobi.profiling.dao.ISbiAttributeDAO; |
| 46 | +import it.eng.spagobi.profiling.dao.ISbiUserDAO; |
| 47 | +import it.eng.spagobi.security.Password; |
| 48 | +import it.eng.spagobi.services.rest.annotations.PublicService; |
| 49 | +import it.eng.spagobi.signup.validation.SignupJWTTokenManager; |
| 50 | +import net.logicsquad.nanocaptcha.image.ImageCaptcha; |
| 51 | +import net.logicsquad.nanocaptcha.image.filter.FishEyeImageFilter; |
| 52 | + |
| 53 | +@Path("2.0/signup") |
| 54 | +public class SignupResource { |
| 55 | + |
| 56 | + private static final Logger LOGGER = Logger.getLogger(SignupResource.class); |
| 57 | + |
| 58 | + @GET |
| 59 | + @Path("/captcha") |
| 60 | + @PublicService |
| 61 | + @Produces(MediaType.APPLICATION_JSON) |
| 62 | + public Response getCaptcha() { |
| 63 | + try { |
| 64 | + int width = 200; |
| 65 | + int height = 75; |
| 66 | + |
| 67 | + ImageCaptcha imageCaptcha = new ImageCaptcha.Builder(width, height).addContent().addBackground().addFilter(new FishEyeImageFilter()).build(); |
| 68 | + |
| 69 | + BufferedImage image = imageCaptcha.getImage(); |
| 70 | + String content = imageCaptcha.getContent(); |
| 71 | + |
| 72 | + String base64Image; |
| 73 | + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { |
| 74 | + ImageIO.write(image, "png", baos); |
| 75 | + base64Image = Base64.getEncoder().encodeToString(baos.toByteArray()); |
| 76 | + } |
| 77 | + |
| 78 | + Map<String, String> response = new HashMap<>(); |
| 79 | + response.put("image", base64Image); |
| 80 | + response.put("content", Password.hashPassword(content)); |
| 81 | + |
| 82 | + return Response.ok(response).build(); |
| 83 | + |
| 84 | + } catch (Exception e) { |
| 85 | + LOGGER.error("Error while generating captcha", e); |
| 86 | + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(Map.of("error", "Error while generating captcha")).build(); |
| 87 | + } |
| 88 | + } |
| 89 | + |
| 90 | + @POST |
| 91 | + @Path("/create") |
| 92 | + @PublicService |
| 93 | + @Produces(MediaType.APPLICATION_JSON) |
| 94 | + public Response create(@Context HttpServletRequest req, Map<String, String> payload) { |
| 95 | + LOGGER.debug("IN"); |
| 96 | + |
| 97 | + try { |
| 98 | + // Check if signup is enabled |
| 99 | + String strActiveSignup = SingletonConfig.getInstance().getConfigValue("SPAGOBI.SECURITY.ACTIVE_SIGNUP_FUNCTIONALITY"); |
| 100 | + boolean activeSignup = "true".equalsIgnoreCase(strActiveSignup); |
| 101 | + if (!activeSignup) { |
| 102 | + LOGGER.error("Attempt to register with signup not active"); |
| 103 | + return Response.status(Response.Status.FORBIDDEN).entity(Map.of("error", "Signup is not enabled")).build(); |
| 104 | + } |
| 105 | + |
| 106 | + // Extract parameters |
| 107 | + String username = payload.get("username"); |
| 108 | + String password = payload.get("password"); |
| 109 | + String confirmPassword = payload.get("confirmPassword"); |
| 110 | + String name = payload.get("name"); |
| 111 | + String surname = payload.get("surname"); |
| 112 | + String email = payload.get("email"); |
| 113 | + String captcha = payload.get("captcha"); |
| 114 | + String content = payload.get("content"); |
| 115 | + |
| 116 | + // Validate inputs |
| 117 | + Response validationError = validateSignupInput(username, password, confirmPassword, email, captcha, content); |
| 118 | + if (validationError != null) { |
| 119 | + return validationError; |
| 120 | + } |
| 121 | + |
| 122 | + // Get default tenant |
| 123 | + String defaultTenant = getDefaultTenant(); |
| 124 | + |
| 125 | + ISbiUserDAO userDao = DAOFactory.getSbiUserDAO(); |
| 126 | + Integer existingUserId = userDao.isUserIdAlreadyInUse(username); |
| 127 | + boolean userRegistrationFromExpiredToken = false; |
| 128 | + |
| 129 | + // Check if user already exists |
| 130 | + if (existingUserId != null) { |
| 131 | + SbiUser sbiUser = userDao.loadSbiUserById(existingUserId); |
| 132 | + boolean matchingEmailAddress = false; |
| 133 | + |
| 134 | + if (sbiUser != null) { |
| 135 | + Set<SbiUserAttributes> userAttributes = sbiUser.getSbiUserAttributeses(); |
| 136 | + for (SbiUserAttributes sbiUserAttributes : userAttributes) { |
| 137 | + if (sbiUserAttributes.getSbiAttribute().getAttributeName().equals("email") && sbiUserAttributes.getAttributeValue().equals(email)) { |
| 138 | + matchingEmailAddress = true; |
| 139 | + break; |
| 140 | + } |
| 141 | + } |
| 142 | + } |
| 143 | + |
| 144 | + userRegistrationFromExpiredToken = matchingEmailAddress && sbiUser.getDtLastAccess() == null; |
| 145 | + |
| 146 | + if (!userRegistrationFromExpiredToken) { |
| 147 | + LOGGER.error("Username already in use"); |
| 148 | + return Response.status(Response.Status.CONFLICT).entity(Map.of("error", "Username already in use")).build(); |
| 149 | + } |
| 150 | + } |
| 151 | + |
| 152 | + // Create user |
| 153 | + SbiUser user = new SbiUser(); |
| 154 | + user.setUserId(username); |
| 155 | + user.setPassword(Password.hashPassword(password)); |
| 156 | + user.setFullName(name + " " + surname); |
| 157 | + user.getCommonInfo().setOrganization(defaultTenant); |
| 158 | + user.getCommonInfo().setUserIn(username); |
| 159 | + user.setFlgPwdBlocked(true); |
| 160 | + |
| 161 | + // Set tenant and role |
| 162 | + Set<SbiExtRoles> roles = new HashSet<>(); |
| 163 | + SbiExtRoles r = new SbiExtRoles(); |
| 164 | + String defaultRole = SingletonConfig.getInstance().getConfigValue("SPAGOBI.SECURITY.DEFAULT_ROLE_ON_SIGNUP"); |
| 165 | + |
| 166 | + IRoleDAO roleDAO = DAOFactory.getRoleDAO(); |
| 167 | + roleDAO.setTenant(defaultTenant); |
| 168 | + Role signupRole = roleDAO.loadByName(defaultRole); |
| 169 | + |
| 170 | + if (signupRole == null) { |
| 171 | + LOGGER.error("Invalid role " + defaultRole + " for the new user"); |
| 172 | + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(Map.of("error", "Invalid default role configuration")).build(); |
| 173 | + } |
| 174 | + |
| 175 | + r.changeExtRoleId(signupRole.getId()); |
| 176 | + r.getCommonInfo().setOrganization(defaultTenant); |
| 177 | + roles.add(r); |
| 178 | + user.setSbiExtUserRoleses(roles); |
| 179 | + |
| 180 | + // Set user attributes |
| 181 | + Set<SbiUserAttributes> attributes = new HashSet<>(); |
| 182 | + ISbiAttributeDAO attrDao = DAOFactory.getSbiAttributeDAO(); |
| 183 | + attrDao.setTenant(defaultTenant); |
| 184 | + |
| 185 | + addAttribute(attributes, attrDao.loadSbiAttributeByName("email").getAttributeId(), email, defaultTenant); |
| 186 | + |
| 187 | + user.setSbiUserAttributeses(attributes); |
| 188 | + if (userRegistrationFromExpiredToken) { |
| 189 | + user.changeId(existingUserId); |
| 190 | + } |
| 191 | + |
| 192 | + int id = userDao.fullSaveOrUpdateSbiUser(user); |
| 193 | + LOGGER.debug("User [" + username + "] successfully created with id [" + id + "]"); |
| 194 | + |
| 195 | + // Send activation email |
| 196 | + try { |
| 197 | + sendActivationEmail(req, user, username); |
| 198 | + } catch (Exception e) { |
| 199 | + LOGGER.error("Cannot send activation email to [" + email + "]", e); |
| 200 | + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(Map.of("error", "Error while sending activation email")).build(); |
| 201 | + } |
| 202 | + |
| 203 | + LOGGER.debug("OUT"); |
| 204 | + return Response.ok(Map.of("message", "User registered successfully. Please check your email for activation")).build(); |
| 205 | + |
| 206 | + } catch (Exception e) { |
| 207 | + LOGGER.error("Error during user creation", e); |
| 208 | + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(Map.of("error", "An error occurred during registration")).build(); |
| 209 | + } |
| 210 | + } |
| 211 | + |
| 212 | + @POST |
| 213 | + @Path("/active") |
| 214 | + @PublicService |
| 215 | + @Produces(MediaType.APPLICATION_JSON) |
| 216 | + public Response active(@Context HttpServletRequest req, Map<String, String> payload) { |
| 217 | + LOGGER.debug("IN"); |
| 218 | + String token = payload.get("token"); |
| 219 | + |
| 220 | + if (StringUtils.isBlank(token)) { |
| 221 | + return Response.status(Response.Status.BAD_REQUEST).entity(Map.of("error", "Token is mandatory")).build(); |
| 222 | + } |
| 223 | + |
| 224 | + try { |
| 225 | + String userId = SignupJWTTokenManager.verifyJWTToken(token); |
| 226 | + |
| 227 | + ISbiUserDAO userDao = DAOFactory.getSbiUserDAO(); |
| 228 | + SbiUser user = userDao.loadSbiUserByUserId(userId); |
| 229 | + |
| 230 | + if (user == null) { |
| 231 | + return Response.status(Response.Status.BAD_REQUEST).entity(Map.of("error", "User not found")).build(); |
| 232 | + } |
| 233 | + |
| 234 | + if (!user.getFlgPwdBlocked()) { |
| 235 | + return Response.status(Response.Status.BAD_REQUEST).entity(Map.of("error", "User already active")).build(); |
| 236 | + } |
| 237 | + |
| 238 | + user.setFlgPwdBlocked(false); |
| 239 | + userDao.updateSbiUser(user, null); |
| 240 | + |
| 241 | + LOGGER.debug("OUT"); |
| 242 | + return Response.ok(Map.of("message", "User activated successfully")).build(); |
| 243 | + |
| 244 | + } catch (TokenExpiredException te) { |
| 245 | + LOGGER.error("Expired Token [" + token + "]", te); |
| 246 | + return Response.status(Response.Status.UNAUTHORIZED).entity(Map.of("error", "Token expired", "expired", true)).build(); |
| 247 | + } catch (Exception e) { |
| 248 | + LOGGER.error("Generic token validation error [" + token + "]", e); |
| 249 | + return Response.status(Response.Status.BAD_REQUEST).entity(Map.of("error", "Invalid token")).build(); |
| 250 | + } |
| 251 | + } |
| 252 | + |
| 253 | + private String getDefaultTenant() { |
| 254 | + String defaultTenant = SingletonConfig.getInstance().getConfigValue("SPAGOBI.SECURITY.DEFAULT_TENANT_ON_SIGNUP"); |
| 255 | + if (defaultTenant == null) { |
| 256 | + LOGGER.debug("DEFAULT_TENANT_ON_SIGNUP not configured, using default value"); |
| 257 | + defaultTenant = "DEFAULT_TENANT"; |
| 258 | + } |
| 259 | + LOGGER.debug("Default tenant set to: " + defaultTenant); |
| 260 | + return defaultTenant; |
| 261 | + } |
| 262 | + |
| 263 | + private Response validateSignupInput(String username, String password, String confirmPassword, String email, String captcha, String content) { |
| 264 | + if (StringUtils.isBlank(username)) { |
| 265 | + return Response.status(Response.Status.BAD_REQUEST).entity(Map.of("error", "Username is mandatory")).build(); |
| 266 | + } |
| 267 | + |
| 268 | + if (StringUtils.isBlank(password) || StringUtils.isBlank(confirmPassword)) { |
| 269 | + return Response.status(Response.Status.BAD_REQUEST).entity(Map.of("error", "Password and confirm password are mandatory")).build(); |
| 270 | + } |
| 271 | + |
| 272 | + if (!password.equals(confirmPassword)) { |
| 273 | + return Response.status(Response.Status.BAD_REQUEST).entity(Map.of("error", "Passwords do not match")).build(); |
| 274 | + } |
| 275 | + |
| 276 | + try { |
| 277 | + PasswordChecker.getInstance().isValid(password, password); |
| 278 | + } catch (EMFUserError e) { |
| 279 | + LOGGER.error("Password is not valid", e); |
| 280 | + return Response.status(Response.Status.BAD_REQUEST).entity(Map.of("error", "Password is not valid")).build(); |
| 281 | + } catch (Exception e) { |
| 282 | + LOGGER.error("Password is not valid", e); |
| 283 | + return Response.status(Response.Status.BAD_REQUEST).entity(Map.of("error", "Password is not valid")).build(); |
| 284 | + } |
| 285 | + |
| 286 | + if (StringUtils.isBlank(email)) { |
| 287 | + return Response.status(Response.Status.BAD_REQUEST).entity(Map.of("error", "Email is mandatory")).build(); |
| 288 | + } |
| 289 | + |
| 290 | + if (StringUtils.isBlank(captcha)) { |
| 291 | + return Response.status(Response.Status.BAD_REQUEST).entity(Map.of("error", "Captcha is required")).build(); |
| 292 | + } |
| 293 | + |
| 294 | + if (StringUtils.isBlank(content)) { |
| 295 | + return Response.status(Response.Status.BAD_REQUEST).entity(Map.of("error", "Captcha content is missing")).build(); |
| 296 | + } |
| 297 | + |
| 298 | + if (!content.equals(Password.hashPassword(captcha))) { |
| 299 | + return Response.status(Response.Status.BAD_REQUEST).entity(Map.of("error", "Invalid captcha")).build(); |
| 300 | + } |
| 301 | + |
| 302 | + return null; |
| 303 | + } |
| 304 | + |
| 305 | + private void sendActivationEmail(HttpServletRequest req, SbiUser user, String username) throws Exception { |
| 306 | + String host = req.getServerName(); |
| 307 | + int port = req.getServerPort(); |
| 308 | + |
| 309 | + String mailText = Resources.toString(getClass().getResource("/templates/confirmationMailTemplate.html"), StandardCharsets.UTF_8); |
| 310 | + String subject = "Activation Request"; |
| 311 | + |
| 312 | + String token = SignupJWTTokenManager.createJWTToken(user.getUserId()); |
| 313 | + String version = SbiCommonInfo.getVersion().substring(0, SbiCommonInfo.getVersion().lastIndexOf(".")); |
| 314 | + |
| 315 | + String urlString = req.getContextPath() + "/restful-services/signup/prepareActive?token=" + token + "&version=" + version; |
| 316 | + URL url = new URL(req.getScheme(), host, port, urlString); |
| 317 | + |
| 318 | + // Replace placeholders in template |
| 319 | + mailText = mailText.replace("%%WELCOME%%", "Welcome to our platform"); |
| 320 | + mailText = mailText.replace("%%USERNAME%%", username); |
| 321 | + mailText = mailText.replace("%%THANKS_MESSAGE%%", "Thank you for registering"); |
| 322 | + mailText = mailText.replace("%%WELCOME_MESSAGE%%", "Please activate your account by clicking the link below"); |
| 323 | + mailText = mailText.replace("%%URL%%", url.toString()); |
| 324 | + mailText = mailText.replace("%%URL_LABEL%%", "Activate Account"); |
| 325 | + mailText = mailText.replace("%%BOOKMARK%%", "Bookmark our website"); |
| 326 | + mailText = mailText.replace("%%QA%%", "Visit our Q&A section"); |
| 327 | + mailText = mailText.replace("%%GITHUB%%", "Visit our GitHub"); |
| 328 | + mailText = mailText.replace("%%DOCUMENTATION%%", "Read the documentation"); |
| 329 | + |
| 330 | + LOGGER.debug("Activation url is equal to [" + url.toExternalForm() + "]"); |
| 331 | + LOGGER.debug("Activation mail for user [" + username + "] successfully prepared"); |
| 332 | + |
| 333 | + sendMail(user.getSbiUserAttributeses().iterator().next().getAttributeValue(), subject, mailText); |
| 334 | + } |
| 335 | + |
| 336 | + private void addAttribute(Set<SbiUserAttributes> attributes, int attrId, String attrValue, String defaultTenant) { |
| 337 | + if (attrValue != null && !attrValue.isEmpty()) { |
| 338 | + SbiUserAttributes a = new SbiUserAttributes(); |
| 339 | + a.getCommonInfo().setOrganization(defaultTenant); |
| 340 | + SbiUserAttributesId id = new SbiUserAttributesId(attrId); |
| 341 | + a.setId(id); |
| 342 | + a.setAttributeValue(attrValue); |
| 343 | + attributes.add(a); |
| 344 | + } |
| 345 | + } |
| 346 | + |
| 347 | + private void sendMail(String emailAddress, String subject, String emailContent) throws Exception { |
| 348 | + MessageMailDto messageMailDto = new MessageMailDto(); |
| 349 | + messageMailDto.setProfileName(ProfileNameMailEnum.USER); |
| 350 | + messageMailDto.setTypeMailEnum(TypeMailEnum.CONTENT); |
| 351 | + messageMailDto.setSubject(subject); |
| 352 | + messageMailDto.setText(emailContent); |
| 353 | + messageMailDto.setContentType("text/html"); |
| 354 | + messageMailDto.setRecipients(new String[] { emailAddress }); |
| 355 | + |
| 356 | + FactoryMailSender.getMailSender(SingletonConfig.getInstance().getConfigValue(IMailSender.MAIL_SENDER)).sendMail(messageMailDto); |
| 357 | + } |
| 358 | + |
| 359 | +} |
0 commit comments