All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.restheart.security.mechanisms.TokenBasicAuthMechanism Maven / Gradle / Ivy

/*-
 * ========================LICENSE_START=================================
 * restheart-security
 * %%
 * Copyright (C) 2018 - 2024 SoftInstigate
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see .
 * =========================LICENSE_END==================================
 */
package org.restheart.security.mechanisms;

import java.io.IOException;
import java.nio.charset.Charset;

import org.restheart.configuration.ConfigurationException;
import org.restheart.plugins.Inject;
import org.restheart.plugins.OnInit;
import org.restheart.plugins.PluginsRegistry;
import org.restheart.plugins.RegisterPlugin;
import org.restheart.plugins.security.AuthMechanism;
import org.restheart.plugins.security.TokenManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.undertow.security.api.SecurityContext;
import io.undertow.security.idm.PasswordCredential;
import io.undertow.security.impl.BasicAuthenticationMechanism;
import io.undertow.server.HttpServerExchange;
import io.undertow.util.FlexBase64;
import static io.undertow.util.Headers.AUTHORIZATION;
import static io.undertow.util.Headers.BASIC;
import static io.undertow.util.StatusCodes.UNAUTHORIZED;

/**
 *
 * @author Andrea Di Cesare {@literal }
 *
 * this extends the undertow BasicAuthenticationMechanism and authenticates
 * requests using AuthTokenIdentityManager.
 *
 * if user already authenticated via a different mechanism, a token is
 * generated so that later calls can be use the token instead of the actual
 * password
 *
 */
@RegisterPlugin(name = "tokenBasicAuthMechanism",
                description = "authenticates the requests using the configured Token Manager",
                enabledByDefault = false,
                priority = Integer.MIN_VALUE)
public class TokenBasicAuthMechanism extends BasicAuthenticationMechanism implements AuthMechanism {
    static final Logger LOGGER = LoggerFactory.getLogger(TokenBasicAuthMechanism.class);

    private static final Charset UTF_8 = Charset.forName("UTF-8");

    private static final String BASIC_PREFIX = BASIC + " ";
    private static final int PREFIX_LENGTH = BASIC_PREFIX.length();
    private static final String COLON = ":";

    private TokenManager tokenManager = null;

    @Inject("registry")
    private PluginsRegistry registry;

    private boolean enabled = true;

    private static void clear(final char[] array) {
        for (int i = 0; i < array.length; i++) {
            array[i] = 0x00;
        }
    }

    /**
     *
     * @throws org.restheart.configuration.ConfigurationException
     */
    public TokenBasicAuthMechanism() throws ConfigurationException {
        super("RESTHeart Realm", "tokenBasicAuthMechanism", true);
    }

    @OnInit
    public void init() throws ConfigurationException {
        if (registry.getTokenManager() != null) {
            this.tokenManager = registry.getTokenManager().getInstance();
        } else {
            this.enabled = false;
            LOGGER.warn("tokenBasicAuthMechanism is disabled because no token manager has been enabled. Consequently, it will not attempt to authenticate requests. Please ensure a token manager is active to use this mechanism.");
        }
    }

    @Override
    public AuthenticationMechanismOutcome authenticate(final HttpServerExchange exchange, final SecurityContext securityContext) {
         if (!this.enabled) {
            return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
        }

        var authHeaders = exchange.getRequestHeaders().get(AUTHORIZATION);
        if (authHeaders != null) {
            for (var current : authHeaders) {
                if (current.startsWith(BASIC_PREFIX)) {
                    var base64Challenge = current.substring(PREFIX_LENGTH);
                    String plainChallenge = null;
                    try {
                        var decode = FlexBase64.decode(base64Challenge);
                        plainChallenge = new String(decode.array(), decode.arrayOffset(), decode.limit(), UTF_8);
                    } catch (IOException e) {
                        // nothing to do
                    }
                    int colonPos;
                    if (plainChallenge != null && (colonPos = plainChallenge.indexOf(COLON)) > -1) {
                        var userName = plainChallenge.substring(0, colonPos);
                        var password = plainChallenge.substring(colonPos + 1).toCharArray();

                        var credential = new PasswordCredential(password);
                        try {
                            final AuthenticationMechanismOutcome result;
                            // this is where the token cache comes into play
                            var account = tokenManager.verify(userName, credential);
                            if (account != null) {
                                securityContext.authenticationComplete(account, getMechanismName(), false);
                                result = AuthenticationMechanismOutcome.AUTHENTICATED;
                            } else {
                                result = AuthenticationMechanismOutcome.NOT_ATTEMPTED;
                            }
                            return result;
                        } finally {
                            clear(password);
                        }
                    }

                    return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
                }
            }
        }

        return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
    }

    @Override
    public ChallengeResult sendChallenge(final HttpServerExchange exchange, final SecurityContext securityContext) {
        if (!this.enabled) {
            return new ChallengeResult(false);
        }

        var authHeader = exchange.getRequestHeaders().getFirst(AUTHORIZATION);

        if (authHeader == null) {
            return new ChallengeResult(false); // --> FORBIDDEN
        } else {
            return new ChallengeResult(true, UNAUTHORIZED);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy