net.maritimecloud.mms.server.security.impl.ApacheConfSecurityHandler.java Source code

Java tutorial

Introduction

Here is the source code for net.maritimecloud.mms.server.security.impl.ApacheConfSecurityHandler.java

Source

/* Copyright (c) 2011 Danish Maritime Authority.
 *
 * Licensed 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 net.maritimecloud.mms.server.security.impl;

import com.typesafe.config.Config;
import net.maritimecloud.mms.server.security.AuthenticationException;
import net.maritimecloud.mms.server.security.AuthenticationHandler;
import net.maritimecloud.mms.server.security.AuthenticationToken;
import org.apache.commons.codec.digest.Crypt;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.codec.digest.Md5Crypt;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * The class implements support for an Apache webserver-style configuration with
 * authentication against a htpasswd file.
 *
 * The class implements the {@code AuthenticationHandler} interface and attempts
 * to authenticate the client from an Apache htpasswd-style credentials file.
 * <p/>
 * The authentication token must be of type {@code UsernamePasswordToken}.
 *
 * <p>The security configuration file must specify the "htpasswd-file" attribute, pointing
 * to an Apache htpasswd-style credentials file</p>
 */
@SuppressWarnings("unused")
public class ApacheConfSecurityHandler implements AuthenticationHandler {

    private final static Pattern HTPASSWD_ENTRY = Pattern.compile("^([^:]+):(.+)");

    private Config conf;

    /** User + encrypted passwords loaded from Apache htpasswd-style file */
    private Map<String, String> userPasswords = new HashMap<>();

    /** Last modified timestamp of the htpasswd file */
    private long htpasswdFileLastModified = -1L;

    /** {@inheritDoc} */
    @Override
    public void init(Config conf) {
        this.conf = conf;
    }

    /** {@inheritDoc} */
    @Override
    public Config getConf() {
        return conf;
    }

    /*************************************************/
    /** Authentication Support                      **/
    /*************************************************/

    /** {@inheritDoc} */
    @Override
    public void authenticate(AuthenticationToken token) throws AuthenticationException {

        if (token == null || !(token instanceof UsernamePasswordToken)) {
            throw new AuthenticationException("Invalid authentication token: " + token);
        }
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;

        try {
            String htpasswdFile = getConf().getString("htpasswd-file");

            if (!authenticate(usernamePasswordToken, new File(htpasswdFile))) {
                throw new AuthenticationException("Invalid username-password");
            }
        } catch (IOException e) {
            throw new AuthenticationException("Error executing htpasswd authentication", e);
        }
    }

    /**
     * Check if the credentials are valid according to the Apache htpasswd-style credentials file
     *
     * @param token the credentials to check
     * @param htpasswdFile the htpasswd file
     * @return if the credentials are valid
     */
    protected boolean authenticate(UsernamePasswordToken token, File htpasswdFile) throws IOException {
        Objects.requireNonNull(token);
        Objects.requireNonNull(htpasswdFile);

        // Read in the htpasswd file
        checkReadHtpasswdFile(htpasswdFile);

        String storedPwd = userPasswords.get(token.getUsername());
        if (storedPwd != null) {
            final String passwd = new String(token.getPassword());

            // test Apache MD5 variant encrypted password
            if (storedPwd.startsWith("$apr1$")) {
                return storedPwd.equals(Md5Crypt.apr1Crypt(passwd, storedPwd));
            }

            // test unsalted SHA password
            else if (storedPwd.startsWith("{SHA}")) {
                String passwd64 = org.apache.commons.codec.binary.Base64
                        .encodeBase64String(DigestUtils.sha1(passwd));
                return storedPwd.substring("{SHA}".length()).equals(passwd64);
            }

            // test libc crypt() encoded password
            else if (storedPwd.equals(Crypt.crypt(passwd, storedPwd))) {
                return true;
            }

            // test clear text
            else if (storedPwd.equals(passwd)) {
                return true;
            }
        }

        // Not authenticated
        return false;
    }

    /**
     * Check if the htpasswd file has been updated since last time it was read.
     * Reads in all users + encrypted password.
     *
     * @param htpasswdFile the password file
     */
    protected synchronized void checkReadHtpasswdFile(File htpasswdFile) throws IOException {
        if (!htpasswdFile.exists()) {
            throw new IOException("File does not exist: " + htpasswdFile);
        }

        // Only read it, if the file has been updated ... and the first time called.
        if (htpasswdFile.lastModified() != htpasswdFileLastModified) {

            userPasswords.clear();
            try (Scanner scanner = new Scanner(new FileInputStream(htpasswdFile))) {
                while (scanner.hasNextLine()) {
                    String line = scanner.nextLine().trim();
                    if (!line.isEmpty() && !line.startsWith("#")) {
                        Matcher m = HTPASSWD_ENTRY.matcher(line);
                        if (m.matches()) {
                            userPasswords.put(m.group(1), m.group(2));
                        }
                    }
                }
            }

            // Record time stamp
            htpasswdFileLastModified = htpasswdFile.lastModified();
        }
    }
}