blob: fd25291bf694d1413470bcb3848f53ce20812c45 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.rave.portal.service.impl;
import org.apache.commons.lang3.StringUtils;
import org.apache.rave.model.PageType;
import org.apache.rave.model.Person;
import org.apache.rave.model.User;
import org.apache.rave.rest.model.SearchResult;
import org.apache.rave.portal.repository.CategoryRepository;
import org.apache.rave.portal.repository.PageRepository;
import org.apache.rave.portal.repository.PageTemplateRepository;
import org.apache.rave.portal.repository.PersonRepository;
import org.apache.rave.portal.repository.UserRepository;
import org.apache.rave.portal.repository.WidgetRepository;
import org.apache.rave.portal.service.EmailService;
import org.apache.rave.portal.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataAccessException;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.codec.Base64;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.openid.OpenIDAuthenticationToken;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
*
*/
@Service(value = "userService")
public class DefaultUserService implements UserService {
private static final Logger log = LoggerFactory.getLogger(DefaultUserService.class);
private final UserRepository userRepository;
private final PageRepository pageRepository;
private final PageTemplateRepository pageTemplateRepository;
private final WidgetRepository widgetRepository;
private final CategoryRepository categoryRepository;
private final PersonRepository personRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private EmailService emailService;
@Value("${portal.mail.passwordservice.subject}")
private String passwordReminderSubject;
@Value("${portal.mail.passwordservice.template}")
private String passwordReminderTemplate;
@Value("${portal.mail.username.subject}")
private String userNameReminderSubject;
@Value("${portal.mail.username.template}")
private String userNameReminderTemplate;
@Value("${portal.mail.service.baseurl}")
private String baseUrl;
@Value("${portal.user.account.admin.subject}")
private String userAccountApprovalSubject;
@Value("${portal.user.account.admin.template}")
private String userAccountApprovalTemplate;
@Value("${portal.user.account.needapproval}")
private boolean userAccountApproval;
@Value("${portal.user.account.admin.email}")
private String approvalAdminEmail;
@Value("${portal.mail.service.loginpage}")
private String loginUrl;
@Autowired
public DefaultUserService(PageRepository pageRepository,
UserRepository userRepository,
WidgetRepository widgetRepository,
PageTemplateRepository pageTemplateRepository,
CategoryRepository categoryRepository,
PersonRepository personRepository) {
this.userRepository = userRepository;
this.pageRepository = pageRepository;
this.widgetRepository = widgetRepository;
this.pageTemplateRepository = pageTemplateRepository;
this.categoryRepository = categoryRepository;
this.personRepository = personRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
log.debug("loadUserByUsername called with: {}", username);
final User user = userRepository.getByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User with username '" + username + "' was not found!");
}
return user;
}
@Override
public User getAuthenticatedUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getPrincipal() instanceof User) {
return (User) authentication.getPrincipal();
} else {
throw new SecurityException("Could not get the authenticated user!");
}
}
@Override
public void setAuthenticatedUser(String userId) {
final User user = userRepository.get(userId);
if (user == null) {
throw new UsernameNotFoundException("User with id '" + userId + "' was not found!");
}
SecurityContext securityContext = createContext(user);
SecurityContextHolder.setContext(securityContext);
}
@Override
public void clearAuthenticatedUser() {
SecurityContextHolder.clearContext();
}
private SecurityContext createContext(final User user) {
SecurityContext securityContext = new SecurityContextImpl();
securityContext.setAuthentication(new AbstractAuthenticationToken(user.getAuthorities()) {
private static final long serialVersionUID = 1L;
@Override
public Object getCredentials() {
return "N/A";
}
@Override
public Object getPrincipal() {
return user;
}
@Override
public boolean isAuthenticated() {
return true;
}
});
return securityContext;
}
@Override
@Transactional
public void registerNewUser(User user) {
if(userAccountApproval){
user.setEnabled(false);
}
User managedUser = userRepository.save(user);
pageRepository.createPageForUser(managedUser, pageTemplateRepository.getDefaultPage(PageType.PERSON_PROFILE.toString()));
if(userAccountApproval && !approvalAdminEmail.isEmpty()){
Map<String, Object> templateData = new HashMap<String, Object>();
templateData.put("user", user);
templateData.put("portalUrl", loginUrl);
emailService.sendEmail(approvalAdminEmail, userAccountApprovalSubject, userAccountApprovalTemplate, templateData);
}
}
@Override
public User getUserById(String id) {
return userRepository.get(id);
}
@Override
public User getUserByUsername(String userName) {
return userRepository.getByUsername(userName);
}
@Override
public User getUserByEmail(String userEmail) {
return userRepository.getByUserEmail(userEmail);
}
@Override
public User getUserByOpenId(String openId) {
return userRepository.getByOpenId(openId);
}
@Override
@Transactional
public void updateUserProfile(User user) {
userRepository.save(user);
}
@Override
public SearchResult<User> getAll() {
final int count = userRepository.getCountAll();
final List<User> users = userRepository.getAll();
return new SearchResult<User>(users, count);
}
@Override
public SearchResult<User> getLimitedList(int offset, int pageSize) {
final int count = userRepository.getCountAll();
final List<User> users = userRepository.getLimitedList(offset, pageSize);
final SearchResult<User> searchResult = new SearchResult<User>(users, count);
searchResult.setOffset(offset);
searchResult.setPageSize(pageSize);
return searchResult;
}
@Override
public SearchResult<Person> getLimitedListOfPersons(int offset, int pageSize) {
SearchResult<User> users = getLimitedList(offset, pageSize);
int count = users.getTotalResults();
List<Person> people = new ArrayList<Person>();
Person person = null;
for(User user : users.getResultSet()){
person = user.toPerson();
person.setId(user.getId());
people.add(person);
}
return new SearchResult<Person>(people, count);
}
@Override
public SearchResult<User> getUsersByFreeTextSearch(String searchTerm, int offset, int pageSize) {
final int count = userRepository.getCountByUsernameOrEmail(searchTerm);
final List<User> users = userRepository.findByUsernameOrEmail(searchTerm, offset, pageSize);
final SearchResult<User> searchResult = new SearchResult<User>(users, count);
searchResult.setOffset(offset);
searchResult.setPageSize(pageSize);
return searchResult;
}
@Override
public SearchResult<Person> getPersonsByFreeTextSearch(String searchTerm, int offset, int pageSize) {
SearchResult<User> users = getUsersByFreeTextSearch(searchTerm, offset, pageSize);
int count = users.getTotalResults();
List<Person> people = new ArrayList<Person>();
Person person = null;
for(User user : users.getResultSet()){
person = user.toPerson();
person.setId(user.getId());
people.add(person);
}
return new SearchResult<Person>(people, count);
}
@Override
public List<Person> getAllPeople() {
return personRepository.getAll();
}
@Override
@Transactional
// TODO RAVE-300: add security check that is is called by admin or the user itself
public void deleteUser(String userId) {
log.info("about to delete userId: " + userId);
User user = userRepository.get(userId);
if (user == null) {
log.warn("unable to find userId " + userId + " to delete");
return;
}
final String username = user.getUsername();
// delete all User type pages
int numDeletedPages = pageRepository.deletePages(userId, PageType.USER.toString());
// delete all person pages
int numDeletedPersonPages = pageRepository.deletePages(userId, PageType.PERSON_PROFILE.toString());
// delete all the widget comments
int numWidgetComments = widgetRepository.deleteAllWidgetComments(userId);
// delete all the widget ratings
int numWidgetRatings = widgetRepository.deleteAllWidgetRatings(userId);
// unassign the user from any widgets where they were the owner
int numWidgetsOwned = widgetRepository.unassignWidgetOwner(userId);
// unassign the user from any category records they created or modified
int numCategoriesTouched = categoryRepository.removeFromCreatedOrModifiedFields(userId);
// remove any person associations created with other users
int numAssociationsRemoved = personRepository.removeAllFriendsAndRequests(userId);
// finally delete the user
userRepository.delete(user);
log.info("Deleted user [" + userId + ',' + username + "] - numPages: " + numDeletedPages + ", numPersonPages:" +
numDeletedPersonPages + ", numWidgetComments: " + numWidgetComments + ", numWidgetRatings: " +
numWidgetRatings + ", numWidgetsOwned: " + numWidgetsOwned + ", numCategoriesTouched:" + numCategoriesTouched +
", numAssociationRemoved:" + numAssociationsRemoved);
}
@Override
public List<Person> getAllByAddedWidget(String widgetId) {
List<Person> persons = new ArrayList<Person>();
List<User> users = userRepository.getAllByAddedWidget(widgetId);
for (User u : users) {
persons.add(u.toPerson());
}
return persons;
}
@Override
public void updatePassword(User newUser) {
log.debug("Changing password for user {}", newUser);
User user = userRepository.getByForgotPasswordHash(newUser.getForgotPasswordHash());
if (user == null) {
throw new IllegalArgumentException("Could not find user for forgotPasswordHash " + newUser.getForgotPasswordHash());
}
String saltedHashedPassword = passwordEncoder.encode(newUser.getPassword());
user.setPassword(saltedHashedPassword);
// reset password hash and time
user.setForgotPasswordHash(null);
user.setForgotPasswordTime(null);
userRepository.save(user);
}
@Override
public void sendUserNameReminder(User newUser) {
log.debug("Calling send username {}", newUser);
User user = userRepository.getByUserEmail(newUser.getEmail());
if (user == null) {
throw new IllegalArgumentException("Could not find user for email " + newUser.getEmail());
}
String to = user.getUsername() + " <" + user.getEmail() + '>';
Map<String, Object> templateData = new HashMap<String, Object>();
templateData.put("user", user);
emailService.sendEmail(to, userNameReminderSubject, userNameReminderTemplate, templateData);
}
@Override
public void sendPasswordReminder(User newUser) {
log.debug("Calling send password change link for user {}", newUser);
User user = userRepository.getByUserEmail(newUser.getEmail());
if (user == null) {
throw new IllegalArgumentException("Could not find user for email " + newUser.getEmail());
}
// create user hash:
String input = user.getEmail() + user.getUsername() + String.valueOf(user.getId()) + System.nanoTime();
// hash needs to be URL friendly:
String safeString = new String(Base64.encode(passwordEncoder.encode(input).getBytes()));
String hashedInput = safeString.replaceAll("[/=]", "A");
user.setForgotPasswordHash(hashedInput);
user.setForgotPasswordTime(Calendar.getInstance().getTime());
userRepository.save(user);
String to = user.getUsername() + " <" + user.getEmail() + '>';
Map<String, Object> templateData = new HashMap<String, Object>();
templateData.put("user", user);
templateData.put("reminderUrl", baseUrl + hashedInput);
emailService.sendEmail(to, passwordReminderSubject, passwordReminderTemplate, templateData);
}
@Override
public boolean isValidReminderRequest(String forgotPasswordHash, int nrOfMinutesValid) {
if (StringUtils.isBlank(forgotPasswordHash)) {
return false;
}
User userForHash = userRepository.getByForgotPasswordHash(forgotPasswordHash);
if (userForHash == null) {
return false;
}
Date requestTime = userForHash.getForgotPasswordTime();
Calendar expiredDate = Calendar.getInstance();
expiredDate.add(Calendar.MINUTE, nrOfMinutesValid);
if (requestTime == null || requestTime.after(expiredDate.getTime())) {
// reset, this is invalid state
userForHash.setForgotPasswordHash(null);
userForHash.setForgotPasswordTime(null);
userRepository.save(userForHash);
return false;
}
return true;
}
@Override
@Transactional
public boolean addFriend(String friendUsername, String username) {
return personRepository.addFriend(friendUsername,username);
}
@Override
@Transactional
public void removeFriend(String friendUsername, String username) {
personRepository.removeFriend(friendUsername,username);
}
@Override
public HashMap<String, List<Person>> getFriendsAndRequests(String username) {
return personRepository.findFriendsAndRequests(username);
}
@Override
public List<Person> getFriendRequestsReceived(String username) {
return personRepository.findFriendRequestsReceived(username);
}
@Override
@Transactional
public boolean acceptFriendRequest(String friendUsername, String username) {
return personRepository.acceptFriendRequest(friendUsername,username);
}
@Override
public UserDetails loadUserDetails(OpenIDAuthenticationToken token) throws UsernameNotFoundException {
final String openId = token.getIdentityUrl();
User user = this.getUserByOpenId(openId);
if (user == null) {
log.info("Open ID User with URL "+openId+" was not found!");
throw new UsernameNotFoundException("Open ID User with URL "+openId+" was not found!");
}
return user;
}
}