package org.cloudfoundry.identity.uaa.scim.endpoints; import com.fasterxml.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.account.EmailChange; import org.cloudfoundry.identity.uaa.account.EmailChangeResponse; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.error.UaaException; import org.cloudfoundry.identity.uaa.resources.QueryableResourceManager; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.scim.event.UserModifiedEvent; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.http.ResponseEntity; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import java.io.IOException; import java.sql.Timestamp; import java.util.List; import java.util.Map; import static org.cloudfoundry.identity.uaa.codestore.ExpiringCodeType.EMAIL; import static org.springframework.http.HttpStatus.CONFLICT; import static org.springframework.http.HttpStatus.CREATED; import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY; @Controller public class ChangeEmailEndpoints implements ApplicationEventPublisherAware { private final ScimUserProvisioning scimUserProvisioning; private final ExpiringCodeStore expiringCodeStore; private ApplicationEventPublisher publisher; private final QueryableResourceManager<ClientDetails> clientDetailsService; private static final int EMAIL_CHANGE_LIFETIME = 30 * 60 * 1000; public static final String CHANGE_EMAIL_REDIRECT_URL = "change_email_redirect_url"; public ChangeEmailEndpoints(ScimUserProvisioning scimUserProvisioning, ExpiringCodeStore expiringCodeStore, QueryableResourceManager<ClientDetails> clientDetailsService) { this.scimUserProvisioning = scimUserProvisioning; this.expiringCodeStore = expiringCodeStore; this.clientDetailsService = clientDetailsService; } @RequestMapping(value="/email_verifications", method = RequestMethod.POST) public ResponseEntity<String> generateEmailVerificationCode(@RequestBody EmailChange emailChange) { String userId = emailChange.getUserId(); String email = emailChange.getEmail(); ScimUser user = scimUserProvisioning.retrieve(userId); if (user.getUserName().equals(user.getPrimaryEmail())) { List<ScimUser> results = scimUserProvisioning.query("userName eq \"" + email + "\" and origin eq \"" + OriginKeys.UAA + "\""); if (!results.isEmpty()) { return new ResponseEntity<>(CONFLICT); } } String code; try { code = expiringCodeStore.generateCode(JsonUtils.writeValueAsString(emailChange), new Timestamp(System.currentTimeMillis() + EMAIL_CHANGE_LIFETIME), EMAIL.name()).getCode(); } catch (JsonUtils.JsonUtilException e) { throw new UaaException("Error while generating change email code", e); } return new ResponseEntity<>(code, CREATED); } @RequestMapping(value="/email_changes", method = RequestMethod.POST) public ResponseEntity<EmailChangeResponse> changeEmail(@RequestBody String code) throws IOException { ExpiringCode expiringCode = expiringCodeStore.retrieveCode(code); if ((null != expiringCode) && ((null == expiringCode.getIntent()) || EMAIL.name().equals(expiringCode.getIntent()))) { Map<String, String> data = JsonUtils.readValue(expiringCode.getData(), new TypeReference<Map<String, String>>() {}); String userId = data.get("userId"); String email = data.get("email"); ScimUser user = scimUserProvisioning.retrieve(userId); if (user.getUserName().equals(user.getPrimaryEmail())) { user.setUserName(email); } user.setPrimaryEmail(email); scimUserProvisioning.update(userId, user); String redirectLocation = null; String clientId = data.get("client_id"); if (clientId != null && !clientId.equals("")) { ClientDetails clientDetails = clientDetailsService.retrieve(clientId); redirectLocation = (String) clientDetails.getAdditionalInformation().get(CHANGE_EMAIL_REDIRECT_URL); } publisher.publishEvent(UserModifiedEvent.emailChanged(userId, user.getUserName(), user.getPrimaryEmail())); EmailChangeResponse emailChangeResponse = new EmailChangeResponse(); emailChangeResponse.setEmail(email); emailChangeResponse.setUserId(userId); emailChangeResponse.setUsername(user.getUserName()); emailChangeResponse.setRedirectUrl(redirectLocation); return new ResponseEntity<>(emailChangeResponse, OK); } else { return new ResponseEntity<>(UNPROCESSABLE_ENTITY); } } @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.publisher = applicationEventPublisher; } }