package org.cloudfoundry.identity.uaa.login; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.error.UaaException; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.type.TypeReference; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.client.BaseClientDetails; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; import org.thymeleaf.context.Context; import org.thymeleaf.spring4.SpringTemplateEngine; import java.io.IOException; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; public class EmailAccountCreationService implements AccountCreationService { public static final String SIGNUP_REDIRECT_URL = "signup_redirect_url"; private final Log logger = LogFactory.getLog(getClass()); private final SpringTemplateEngine templateEngine; private final MessageService messageService; private final RestTemplate uaaTemplate; private final String uaaBaseUrl; private final String brand; private final ObjectMapper objectMapper; private final String baseUrl; public EmailAccountCreationService(ObjectMapper objectMapper, SpringTemplateEngine templateEngine, MessageService messageService, RestTemplate uaaTemplate, String uaaBaseUrl, String brand, String baseUrl) { this.objectMapper = objectMapper; this.templateEngine = templateEngine; this.messageService = messageService; this.uaaTemplate = uaaTemplate; this.uaaBaseUrl = uaaBaseUrl; this.brand = brand; this.baseUrl = baseUrl; } @Override public void beginActivation(String email, String password, String clientId) { String subject = getSubjectText(); try { ScimUser scimUser = createUser(email, password); generateAndSendCode(email, clientId, subject, scimUser.getId()); } catch (HttpClientErrorException e) { String uaaResponse = e.getResponseBodyAsString(); try { ExistingUserResponse existingUserResponse = new ObjectMapper().readValue(uaaResponse, ExistingUserResponse.class); if (existingUserResponse.getVerified()) { throw new UaaException(e.getStatusText(), e.getStatusCode().value()); } generateAndSendCode(email, clientId, subject, existingUserResponse.getUserId()); } catch (IOException ioe) { ioe.printStackTrace(); } } catch (RestClientException e) { logger.error("Exception raised while creating account activation email for " + email, e); } catch (IOException e) { logger.error("Exception raised while creating account activation email for " + email, e); } } private void generateAndSendCode(String email, String clientId, String subject, String userId) throws IOException { Timestamp expiresAt = new Timestamp(System.currentTimeMillis() + (60 * 60 * 1000)); // 1 hour ExpiringCode expiringCodeForPost = getExpiringCode(userId, clientId, expiresAt); ExpiringCode expiringCode = uaaTemplate.postForObject(uaaBaseUrl + "/Codes", expiringCodeForPost, ExpiringCode.class); String htmlContent = getEmailHtml(expiringCode.getCode(), email); messageService.sendMessage(userId, email, MessageType.CREATE_ACCOUNT_CONFIRMATION, subject, htmlContent); } private ExpiringCode getExpiringCode(String userId, String clientId, Timestamp expiresAt) throws IOException { Map<String, String> codeData = new HashMap<>(); codeData.put("user_id", userId); codeData.put("client_id", clientId); String codeDataString = objectMapper.writeValueAsString(codeData); return new ExpiringCode(null, expiresAt, codeDataString); } @Override public AccountCreationResponse completeActivation(String code) throws IOException { ExpiringCode expiringCode = uaaTemplate.getForObject(uaaBaseUrl + "/Codes/"+ code, ExpiringCode.class); Map<String, String> data = objectMapper.readValue(expiringCode.getData(), new TypeReference<Map<String, String>>() {}); ScimUser user = uaaTemplate.getForObject(uaaBaseUrl + "/Users/" + data.get("user_id") + "/verify", ScimUser.class); ClientDetails clientDetails = uaaTemplate.getForObject(uaaBaseUrl + "/oauth/clients/" + data.get("client_id"), BaseClientDetails.class); String redirectLocation = (String) clientDetails.getAdditionalInformation().get(SIGNUP_REDIRECT_URL); return new AccountCreationResponse(user.getId(), user.getUserName(), user.getUserName(), redirectLocation); } @Override public void resendVerificationCode(String email, String clientId) { String url = uaaBaseUrl + "/ids/Users?attributes=id&filter=userName eq \"" + email + "\" and origin eq \"" + Origin.UAA + "\""; Map<String,Object> response = uaaTemplate.getForObject(url, Map.class); List<Map<String,String>> resources = (ArrayList)response.get("resources"); String userId = resources.get(0).get("id"); try { generateAndSendCode(email, clientId, getSubjectText(), userId); } catch (IOException e) { logger.error("Exception raised while resending activation email for " + email, e); } } @Override public ScimUser createUser(String username, String password) { ScimUser scimUser = new ScimUser(); scimUser.setUserName(username); ScimUser.Email email = new ScimUser.Email(); email.setPrimary(true); email.setValue(username); scimUser.setEmails(Arrays.asList(email)); scimUser.setOrigin(Origin.UAA); scimUser.setPassword(password); ScimUser userResponse = uaaTemplate.postForObject(uaaBaseUrl + "/Users", scimUser, ScimUser.class); return userResponse; } private String getSubjectText() { return brand.equals("pivotal") ? "Activate your Pivotal ID" : "Activate your account"; } private String getEmailHtml(String code, String email) { String accountsUrl = baseUrl + "/verify_user"; final Context ctx = new Context(); ctx.setVariable("serviceName", brand.equals("pivotal") ? "Pivotal" : "Cloud Foundry"); ctx.setVariable("servicePhrase", brand.equals("pivotal") ? "a Pivotal ID" : "an account"); ctx.setVariable("code", code); ctx.setVariable("email", email); ctx.setVariable("accountsUrl", accountsUrl); return templateEngine.process("activate", ctx); } }