org.madsonic.service.SecurityService.java Source code

Java tutorial

Introduction

Here is the source code for org.madsonic.service.SecurityService.java

Source

/*
 This file is part of Subsonic.
    
 Subsonic is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
    
 Subsonic 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 General Public License
 along with Subsonic.  If not, see <http://www.gnu.org/licenses/>.
    
 Copyright 2009 (C) Sindre Mehus
 */
package org.madsonic.service;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.RandomStringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.authentication.dao.SaltSource;
import org.springframework.security.authentication.encoding.PasswordEncoder;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestWrapper;

import net.sf.ehcache.Ehcache;

import org.madsonic.Logger;
import org.madsonic.dao.AccessRightDao;
import org.madsonic.dao.GroupDao;
import org.madsonic.dao.UserDao;
import org.madsonic.domain.AccessGroup;
import org.madsonic.domain.AccessToken;
import org.madsonic.domain.Group;
import org.madsonic.domain.MusicFolder;
import org.madsonic.domain.TranscodeScheme;
import org.madsonic.domain.User;
import org.madsonic.domain.UserSettings;
import org.madsonic.util.FileUtil;

/**
 * Provides security-related services for authentication and authorization.
 *
 * @author Sindre Mehus
 */

@SuppressWarnings("deprecation")
public class SecurityService implements InitializingBean, UserDetailsService {

    private static final Logger LOG = Logger.getLogger(SecurityService.class);

    private GroupDao groupDao;
    private AccessRightDao accessRightDao;
    private UserDao userDao;
    private SettingsService settingsService;
    private Ehcache userCache;

    @SuppressWarnings("deprecation")
    private PasswordEncoder passwordEncoder;
    private SaltSource saltSource;

    /**
     * After instantiation, check if this is an old Subsonic instance, still
     * storing passwords in clear-text. If so, update to salted hashing.
     */
    public void afterPropertiesSet() throws Exception {
        JdbcTemplate template = userDao.getJdbcTemplate();

        if (template.queryForInt("select count(*) from version where version = 160") == 0) {
            LOG.info("Updating database schema to version 160. (securing user passwords)");
            template.execute("insert into version values (160)");

            for (User user : getAllUsers()) {
                setSecurePassword(user);
                updateUser(user);
            }
        }
    }

    public void updateSecurePassword(User user) {
        setSecurePassword(user);
        updateUser(user);
    }

    @SuppressWarnings("deprecation")
    public void setSecurePassword(User user) {
        UserDetails userDetails = new org.springframework.security.core.userdetails.User(user.getUsername(),
                user.getPassword(), new ArrayList<GrantedAuthority>());
        String encodedPassword = passwordEncoder.encodePassword(userDetails.getPassword(),
                saltSource.getSalt(userDetails));
        user.setPassword(encodedPassword);
    }

    /**
     * Locates the user based on the username.
     *
     * @param username The username presented to the {@link DaoAuthenticationProvider}
     * @return A fully populated user record (never <code>null</code>)
     * @throws UsernameNotFoundException if the user could not be found or the user has no GrantedAuthority.
     * @throws DataAccessException       If user could not be found for a repository-specific reason.
     */
    @SuppressWarnings("deprecation")
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {

        boolean unlocked = true;
        User user = getUserByName(username);

        if (user == null) {
            throw new UsernameNotFoundException("User \"" + username + "\" was not found.");
        }
        // block disabled user at logon
        if (username.equalsIgnoreCase("default") || user.isLocked()) {
            unlocked = false;
        }
        String[] roles = userDao.getRolesForUser(username);
        List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(roles.length);
        for (int i = 0; i < roles.length; i++) {
            authorities.add(new GrantedAuthorityImpl("ROLE_" + roles[i].toUpperCase()));
        }
        // If user is LDAP authenticated, disable user. The proper authentication should in that case
        // be done by SubsonicLdapBindAuthenticator.
        boolean enabled = !user.isLdapAuthenticated();

        return new org.springframework.security.core.userdetails.User(username, user.getPassword(), enabled, true,
                true, unlocked, authorities);
    }

    /**
     * Returns the currently logged-in user for the given HTTP request.
     *
     * @param request The HTTP request.
     * @return The logged-in user, or <code>null</code>.
     */
    public User getCurrentUser(HttpServletRequest request) {
        String username = getCurrentUsername(request);
        return username == null ? null : userDao.getUserByName(username);
    }

    /**
     * Returns the name of the currently logged-in user.
     *
     * @param request The HTTP request.
     * @return The name of the logged-in user, or <code>null</code>.
     */
    public String getCurrentUsername(HttpServletRequest request) {
        return new SecurityContextHolderAwareRequestWrapper(request, null).getRemoteUser();
    }

    /**
     * Returns the user with the given username.
     *
     * @param username The username used when logging in.
     * @return The user, or <code>null</code> if not found.
     */
    public User getUserByName(String username) {
        return userDao.getUserByName(username);
    }

    /**
     * Returns the user with the given email address.
     *
     * @param email The email address.
     * @return The user, or <code>null</code> if not found.
     */
    public User getUserByEmail(String email) {
        return userDao.getUserByEmail(email);
    }

    /**
     * Returns the GroupId with the given email address.
     *
     * @param user.
     * @return if found integer or <code>null</code> if not found.
     */
    public int getCurrentUserGroupId(HttpServletRequest request) {
        String username = getCurrentUsername(request);
        return username == null ? null : userDao.getUserAccess(username);
    }

    public AccessGroup getAccessGroup() {
        AccessGroup accessGroup = new AccessGroup();
        List<Group> allGroups = groupDao.getAllGroups();
        accessGroup = accessRightDao.getAllAccessToken(allGroups);
        return accessGroup;
    }

    public List<AccessToken> getAllAccessToken() {
        AccessGroup accessGroup = new AccessGroup();
        List<Group> allGroups = groupDao.getAllGroups();
        accessGroup = accessRightDao.getAllAccessToken(allGroups);
        return accessGroup.getAccessToken();
    }

    public void updateAccessRight(int user_group_id, int music_folder_id, boolean isEnabled) {
        accessRightDao.updateAccessRight(user_group_id, music_folder_id, isEnabled);
    }

    /**
     * Returns all users.
     *
     * @return Possibly empty array of all users.
     */
    public List<User> getAllUsers() {
        return userDao.getAllUsers();
    }

    /**
     * Returns all groups.
     *
     * @return Possibly empty array of all groups.
     */
    public List<Group> GetAllGroups() {
        return groupDao.getAllGroups();
    }

    /**
     * Returns id from Group.
     *
     * @return Possibly empty array of GroupId.
     */
    public int GetIdfromGroup(Group group) {
        return groupDao.getIdAllGroups(group);
    }

    public void createGroup(Group group) {
        groupDao.createGroup(group);
        LOG.info("## Created Access Group: " + group.getName());
        group.setId(GetIdfromGroup(group));
        groupDao.insertGroupAccess(group.getId());
        LOG.info("## Created Default Access for Group: " + group.getName());
    }

    public int getUserGroupVideoDefault(int id) {
        return groupDao.getUserGroupVideoDefault(id);
    }

    public int getUserGroupVideoMax(int id) {
        return groupDao.getUserGroupVideoMax(id);
    }

    public void updateGroup(Group group) {
        groupDao.updateGroup(group);
        LOG.debug("## Updated Access Group: " + group.getName());
    }

    public void deleteGroup(Group group) {
        groupDao.deleteGroup(group);
        LOG.info("## Deleted Access Group: " + group.getName());
    }

    public void resetControl() {
        groupDao.resetGroup();
        LOG.info("## All Access Groups reseted!!!");
    }

    /**
     * Returns whether the given user has administrative rights.
     */
    public boolean isAdmin(String username) {
        if (User.USERNAME_ADMIN.equals(username)) {
            return true;
        }
        User user = getUserByName(username);
        return user != null && user.isAdminRole();
    }

    public void checkDemoAccount() {

        User demoUser = userDao.getUserByName(User.USERNAME_DEMO);

        // check default User
        if (demoUser == null) {
            userDao.createUser(new User(User.USERNAME_DEMO, RandomStringUtils.randomAlphanumeric(30), null, false,
                    0, 0, 0, 0, true, "#demo user"));
            demoUser = userDao.getUserByName(User.USERNAME_DEMO);
            demoUser.setLdapAuthenticated(false);
            demoUser.setStreamRole(true);
            setSecurePassword(demoUser);
            demoUser.setLocked(true);
            userDao.updateUser(demoUser);
            userDao.updateUserSettings(settingsService.getUserSettings(User.USERNAME_DEMO));
            LOG.debug("## Created user " + User.USERNAME_DEMO);

        } else {
            UserSettings userSettings = userDao.getUserSettings(User.USERNAME_DEMO);
            demoUser.setLdapAuthenticated(false);
            demoUser.setStreamRole(false);
            demoUser.setLocked(true);
            userDao.updateUser(demoUser);
            userDao.updateUserSettings(userSettings);
            LOG.debug("## Updated user " + User.USERNAME_DEMO);
        }
    }

    /**
     * check/creates a default/guest user.
     */
    public void checkAccounts() {

        User defaultUser = userDao.getUserByName(User.USERNAME_DEFAULT);
        User guestUser = userDao.getUserByName(User.USERNAME_GUEST);

        // check default User
        if (defaultUser == null) {
            userDao.createUser(new User(User.USERNAME_DEFAULT, RandomStringUtils.randomAlphanumeric(30), null,
                    false, 0, 0, 0, 0, true, "#default user"));
            defaultUser = userDao.getUserByName(User.USERNAME_DEFAULT);
            defaultUser.setLdapAuthenticated(false);
            defaultUser.setStreamRole(false);
            setSecurePassword(defaultUser);
            userDao.updateUser(defaultUser);
            userDao.updateUserSettings(settingsService.getUserSettings(User.USERNAME_DEFAULT));
            LOG.debug("## Created user " + User.USERNAME_DEFAULT);

        } else {
            UserSettings userSettings = userDao.getUserSettings(User.USERNAME_DEFAULT);
            defaultUser.setLdapAuthenticated(false);
            defaultUser.setStreamRole(false);
            defaultUser.setLocked(true);
            userDao.updateUser(defaultUser);
            userDao.updateUserSettings(userSettings);
            LOG.debug("## Updated user " + User.USERNAME_DEFAULT);
        }

        // check guest User
        if (guestUser == null) {
            userDao.createUser(new User(User.USERNAME_GUEST, RandomStringUtils.randomAlphanumeric(30), null, false,
                    0, 0, 0, 1, false, "#guest account"));
            guestUser = userDao.getUserByName(User.USERNAME_GUEST);
            guestUser.setLdapAuthenticated(false);
            guestUser.setStreamRole(true);
            setSecurePassword(guestUser);
            userDao.updateUser(guestUser);
            userDao.updateUserSettings(settingsService.getUserSettings(User.USERNAME_GUEST));
            UserSettings userSettings = userDao.getUserSettings(User.USERNAME_GUEST);
            userSettings.setTranscodeScheme(TranscodeScheme.MAX_192);
            userDao.updateUserSettings(userSettings);

            LOG.debug("## Created user " + User.USERNAME_GUEST);
        } else {
            UserSettings userSettings = userDao.getUserSettings(User.USERNAME_GUEST);
            guestUser.setLdapAuthenticated(false);
            guestUser.setStreamRole(true);
            userDao.updateUserSettings(userSettings);
            LOG.debug("## Updated user " + User.USERNAME_GUEST);
        }
    }

    /**
     * Creates a new user.
     *
     * @param user The user to create.
     */
    public void createUser(User user) {
        userDao.createUser(user);
        LOG.info("## Created user " + user.getUsername());
    }

    public void cloneUser(User user) {
        userDao.createUser(user);
        userDao.updateUserSettings(settingsService.getDefaultUserSettings(user.getUsername()));
        LOG.info("## Cloned from default user: " + user.getUsername());
    }

    /**
     * Deletes the user with the given username.
     *
     * @param username The username.
     */
    public void deleteUser(String username) {
        userDao.deleteUser(username);
        LOG.info("## Deleted user " + username);
        userCache.remove(username);
    }

    /**
     * Updates the given user.
     *
     * @param user The user to update.
     */
    public void updateUser(User user) {
        userDao.updateUser(user);
        userCache.remove(user.getUsername());
    }

    /**
     * Updates the byte counts for given user.
     *
     * @param user                 The user to update, may be <code>null</code>.
     * @param bytesStreamedDelta   Increment bytes streamed count with this value.
     * @param bytesDownloadedDelta Increment bytes downloaded count with this value.
     * @param bytesUploadedDelta   Increment bytes uploaded count with this value.
     */
    public void updateUserByteCounts(User user, long bytesStreamedDelta, long bytesDownloadedDelta,
            long bytesUploadedDelta) {
        if (user == null) {
            return;
        }

        user.setBytesStreamed(user.getBytesStreamed() + bytesStreamedDelta);
        user.setBytesDownloaded(user.getBytesDownloaded() + bytesDownloadedDelta);
        user.setBytesUploaded(user.getBytesUploaded() + bytesUploadedDelta);

        userDao.updateUser(user);
    }

    public boolean isAccessAllowed(File file, int user_group_id) {
        return isInMusicFolder(file, user_group_id) || isInPodcastFolder(file);
        //TODO:isAccessAllowed
    }

    public boolean isReadAllowed(File file) {
        // Allowed to read from both music folder and podcast folder.
        return isInMusicFolder(file) || isInPodcastFolder(file);
    }

    /**
     * Returns whether the given file may be read.
     *
     * @return Whether the given file may be read.
     */
    public boolean isReadAllowed(File file, int user_group_id) {
        // Allowed to read from both music folder and podcast folder.
        return isInMusicFolder(file, user_group_id) || isInPodcastFolder(file);
    }

    /**
     * Returns whether the given file may be written, created or deleted.
     *
     * @return Whether the given file may be written, created or deleted.
     */
    public boolean isWriteAllowed(File file) {
        // Only allowed to write podcasts, artist art or cover art.
        boolean isPodcast = isInPodcastFolder(file);
        boolean isCoverArt = isInMusicFolder(file) && file.getName().toLowerCase().startsWith("cover.");
        boolean isArtistArt = isInMusicFolder(file) && file.getName().toLowerCase().startsWith("artist.");

        return isPodcast || isCoverArt || isArtistArt;
    }

    /**
     * Returns whether the given file may be uploaded.
     *
     * @return Whether the given file may be uploaded.
     */
    public boolean isUploadAllowed(File file) {
        return isInUploadFolder(file) || isInMusicFolder(file) && !FileUtil.exists(file);
    }

    /**
     * Returns whether the given file is located in the upload folders.
     *
     * @param file The file in question.
     * @return Whether the given file is located in the upload folders.
     */
    private boolean isInUploadFolder(File file) {
        if (isFileInFolder(file.getPath(), SettingsService.getUploadFolder())) {
            return true;
        }
        return false;
    }

    /**
      * Returns whether the given file is located in one of the music folders (or any of their sub-folders).
      *
      * @param file The file in question.
      * @return Whether the given file is located in one of the music folders.
      */
    private boolean isInMusicFolder(File file, int user_group_id) {
        return getMusicFolderForFile(file, user_group_id) != null;
    }

    private MusicFolder getMusicFolderForFile(File file, int user_group_id) {
        List<MusicFolder> folders = settingsService.getAllMusicFolders(false, true, user_group_id);
        String path = file.getPath();
        for (MusicFolder folder : folders) {
            if (isFileInFolder(path, folder.getPath().getPath())) {
                return folder;
            }
        }
        return null;
    }

    private boolean isInMusicFolder(File file) {
        return getMusicFolderForFile(file) != null;
    }

    private MusicFolder getMusicFolderForFile(File file) {
        if (file == null) {
            return null;
        }

        List<MusicFolder> folders = settingsService.getAllMusicFolders(false, true);
        String path = file.getPath();
        for (MusicFolder folder : folders) {
            if (isFileInFolder(path, folder.getPath().getPath())) {
                return folder;
            }
        }
        return null;
    }

    /**
     * Returns whether the given file is located in the Podcast folder (or any of its sub-folders).
     *
     * @param file The file in question.
     * @return Whether the given file is located in the Podcast folder.
     */
    private boolean isInPodcastFolder(File file) {
        if (file == null) {
            return false;
        }

        String podcastFolder = settingsService.getPodcastFolder();
        return isFileInFolder(file.getPath(), podcastFolder);
    }

    public String getRootFolderForFile(File file) {
        MusicFolder folder = getMusicFolderForFile(file, 0);
        if (folder != null) {
            return folder.getPath().getPath();
        }

        if (isInPodcastFolder(file)) {
            return settingsService.getPodcastFolder();
        }
        return null;
    }

    /**
     * Returns whether the given file is located in the given folder (or any of its sub-folders).
     * If the given file contains the expression ".." (indicating a reference to the parent directory),
     * this method will return <code>false</code>.
     *
     * @param file   The file in question.
     * @param folder The folder in question.
     * @return Whether the given file is located in the given folder.
     */
    protected boolean isFileInFolder(String file, String folder) {
        // Deny access if file contains ".." surrounded by slashes (or end of line).
        if (file.matches(".*(/|\\\\)\\.\\.(/|\\\\|$).*")) {
            return false;
        }

        // Convert slashes.
        file = file.replace('\\', '/');
        folder = folder.replace('\\', '/');

        return file.toUpperCase().startsWith(folder.toUpperCase());
    }

    public void setSettingsService(SettingsService settingsService) {
        this.settingsService = settingsService;
    }

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void setGroupDao(GroupDao groupDao) {
        this.groupDao = groupDao;
    }

    public void setAccessRightDao(AccessRightDao accessRightDao) {
        this.accessRightDao = accessRightDao;
    }

    public void setUserCache(Ehcache userCache) {
        this.userCache = userCache;
    }

    @SuppressWarnings("deprecation")
    public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }

    public void setSaltSource(SaltSource saltSource) {
        this.saltSource = saltSource;
    }
}