org.rapidcontext.core.security.SecurityContext.java Source code

Java tutorial

Introduction

Here is the source code for org.rapidcontext.core.security.SecurityContext.java

Source

/*
 * RapidContext <http://www.rapidcontext.com/>
 * Copyright (c) 2007-2015 Per Cederberg. All rights reserved.
 *
 * This program is free software: you can redistribute it and/or
 * modify it under the terms of the BSD license.
 *
 * This program 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 RapidContext LICENSE for more details.
 */

package org.rapidcontext.core.security;

import java.util.logging.Logger;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateUtils;
import org.rapidcontext.core.storage.Storage;
import org.rapidcontext.core.storage.StorageException;
import org.rapidcontext.core.type.Role;
import org.rapidcontext.core.type.User;
import org.rapidcontext.util.BinaryUtil;

/**
 * The application security context. This class provides static
 * methods for authentication and resource authorization. It stores
 * the currently authenticated user in a thread-local storage, so
 * user credentials must be provided separately for each execution
 * thread. It is important that the manager is initialized before
 * any authentication calls are made, or they will fail.
 *
 * @author   Per Cederberg
 * @version  1.0
 */
public class SecurityContext {

    /**
     * The class logger.
     */
    private static final Logger LOG = Logger.getLogger(SecurityContext.class.getName());

    /**
     * The data storage used for reading and writing configuration
     * data.
     */
    private static Storage dataStorage = null;

    /**
     * The currently authenticated users. This is a thread-local
     * variable containing one user object per thread. If the value
     * is set to null, no user is currently authenticated for the
     * thread.
     */
    private static ThreadLocal authUser = new ThreadLocal();

    /**
     * The cache of all user roles available. This array will be reset
     * on each call to init(), i.e. only when the system is reset.
     *
     * @see #init(Storage)
     */
    private static Role[] roleCache = null;

    /**
     * Initializes the security context. It can be called multiple
     * times in order to re-read the configuration data from the
     * data storage. The data store specified will be used for
     * reading and writing users and roles both during initialization
     * and later.
     *
     * @param storage        the data storage to use
     *
     * @throws StorageException if the storage couldn't be read or
     *             written
     */
    public static void init(Storage storage) throws StorageException {
        dataStorage = storage;
        int count = dataStorage.lookupAll(User.PATH).length;
        if (count <= 0) {
            LOG.info("creating default 'admin' user");
            User user = new User("admin");
            user.setName("Administrator User");
            user.setDescription("The default administrator user (automatically created).");
            user.setEnabled(true);
            user.setRoles(new String[] { "admin" });
            User.store(dataStorage, user);
        }
        roleCache = Role.findAll(dataStorage);
    }

    /**
     * Returns the currently authenticated user for this thread.
     *
     * @return the currently authenticated user, or
     *         null if no user is currently authenticated
     */
    public static User currentUser() {
        return (User) authUser.get();
    }

    /**
     * Checks if the currently authenticated user has internal access
     * to a storage path.
     *
     * @param path           the object storage path
     *
     * @return true if the current user has internal access, or
     *         false otherwise
     */
    public static boolean hasInternalAccess(String path) {
        return hasAccess(currentUser(), path, Role.PERM_INTERNAL);
    }

    /**
     * Checks if the currently authenticated user has read access to
     * a storage path.
     *
     * @param path           the object storage path
     *
     * @return true if the current user has read access, or
     *         false otherwise
     */
    public static boolean hasReadAccess(String path) {
        return hasAccess(currentUser(), path, Role.PERM_READ);
    }

    /**
     * Checks if the currently authenticated user has search access to
     * a storage path.
     *
     * @param path           the object storage path
     *
     * @return true if the current user has search access, or
     *         false otherwise
     */
    public static boolean hasSearchAccess(String path) {
        return hasAccess(currentUser(), path, Role.PERM_SEARCH);
    }

    /**
     * Checks if the currently authenticated user has write access to
     * a storage path.
     *
     * @param path           the object storage path
     *
     * @return true if the current user has write access, or
     *         false otherwise
     */
    public static boolean hasWriteAccess(String path) {
        return hasAccess(currentUser(), path, Role.PERM_WRITE);
    }

    /**
     * Checks if the currently authenticated user has has access
     * permission for a storage path.
     *
     * @param path           the object storage path
     * @param permission     the requested permission
     *
     * @return true if the current user has access, or
     *         false otherwise
     *
     * @see Role#hasAccess(String, String)
     */
    public static boolean hasAccess(String path, String permission) {
        return hasAccess(currentUser(), path, permission);
    }

    /**
     * Checks if the specified user has has access permission for a
     * storage path.
     *
     * @param user           the user to check, or null or anonymous
     * @param path           the object storage path
     * @param permission     the requested permission
     *
     * @return true if the current user has access, or
     *         false otherwise
     *
     * @see Role#hasAccess(String, String)
     */
    public static boolean hasAccess(User user, String path, String permission) {
        path = StringUtils.removeStart(path, "/");
        permission = permission.toLowerCase().trim();
        for (int i = 0; i < roleCache.length; i++) {
            Role role = roleCache[i];
            if (role.hasUser(user) && role.hasAccess(path, permission)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Creates a unique number to be used once for hashing.
     *
     * @return the unique hash number
     */
    public static String nonce() {
        return String.valueOf(System.currentTimeMillis());
    }

    /**
     * Verifies that the specified nonce is sufficiently recently
     * generated to be acceptable.
     *
     * @param nonce          the nonce to check
     *
     * @throws SecurityException if the nonce was invalid
     */
    public static void verifyNonce(String nonce) throws SecurityException {
        try {
            long since = System.currentTimeMillis() - Long.parseLong(nonce);
            if (since > DateUtils.MILLIS_PER_MINUTE * 240) {
                LOG.info("stale authentication one-off number");
                throw new SecurityException("stale authentication one-off number");
            }
        } catch (NumberFormatException e) {
            LOG.info("invalid authentication one-off number");
            throw new SecurityException("invalid authentication one-off number");
        }
    }

    /**
     * Authenticates the specified user. This method will verify
     * that the user exists and is enabled. It should only be called
     * if a previous user authentication can be trusted, either via
     * a cookie, command-line login or similar. After a successful
     * authentication the current user will be set to the specified
     * user.
     *
     * @param id             the unique user id
     *
     * @throws SecurityException if the user failed authentication
     */
    public static void auth(String id) throws SecurityException {
        User user = User.find(dataStorage, id);
        String msg;

        if (user == null) {
            msg = "user " + id + " does not exist";
            LOG.info("failed authentication: " + msg);
            throw new SecurityException(msg);
        } else if (!user.isEnabled()) {
            msg = "user " + id + " is disabled";
            LOG.info("failed authentication: " + msg);
            throw new SecurityException(msg);
        }
        authUser.set(user);
    }

    /**
     * Authenticates the specified user with an MD5 two-step hash.
     * This method will verify that the user exists, is enabled and
     * that the password hash plus the specified suffix will MD5 hash
     * to the specified string, After a successful authentication the
     * current user will be set to the specified user.
     *
     * @param id             the unique user id
     * @param suffix         the user password hash suffix to append
     * @param hash           the expected hashed result
     *
     * @throws Exception if the authentication failed
     */
    public static void authHash(String id, String suffix, String hash) throws Exception {

        User user = User.find(dataStorage, id);
        String test;
        String msg;

        if (user == null) {
            msg = "user " + id + " does not exist";
            LOG.info("failed authentication: " + msg);
            throw new SecurityException(msg);
        } else if (!user.isEnabled()) {
            msg = "user " + id + " is disabled";
            LOG.info("failed authentication: " + msg);
            throw new SecurityException(msg);
        }
        test = BinaryUtil.hashMD5(user.passwordHash() + suffix);
        if (user.passwordHash().length() > 0 && !test.equals(hash)) {
            msg = "invalid password for user " + id;
            LOG.info("failed authentication: " + msg + ", expected: " + test + ", received: " + hash);
            throw new SecurityException(msg);
        }
        authUser.set(user);
    }

    /**
     * Authenticates with a user authentication token. This method
     * will verify that the user exists, is enabled and that the
     * token is valid for the current user password. After a
     * successful authentication the current user will be set to the
     * user in the token.
     *
     * @param token          the authentication token
     *
     * @throws Exception if the authentication failed
     */
    public static void authToken(String token) throws Exception {
        String[] parts = User.decodeAuthToken(token);
        String id = parts[0];
        User user = User.find(dataStorage, id);
        long expiry = Long.parseLong(parts[1]);
        String msg;

        if (user == null) {
            msg = "user " + id + " does not exist";
            LOG.info("failed authentication: " + msg);
            throw new SecurityException(msg);
        } else if (!user.isEnabled()) {
            msg = "user " + id + " is disabled";
            LOG.info("failed authentication: " + msg);
            throw new SecurityException(msg);
        } else if (expiry < System.currentTimeMillis()) {
            msg = "token has expired";
            LOG.info("failed authentication: " + msg);
            throw new SecurityException(msg);
        } else if (!user.verifyAuthToken(token)) {
            msg = "invalid auth token for user " + id;
            LOG.info("failed token authentication: " + msg + ", expected: " + user.createAuthToken(expiry)
                    + ", received: " + token);
            throw new SecurityException(msg);
        }
        authUser.set(user);
    }

    /**
     * Removes any previous authentication. I.e. the current user
     * will be reset to the anonymous user.
     */
    public static void authClear() {
        authUser.set(null);
    }
}