/*
 * Copyright 2015 Smartling, Inc.
 *
 * 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 org.keycloak.adapters.springsecurity.userdetails.authentication;

import org.keycloak.KeycloakPrincipal;
import org.keycloak.adapters.OidcKeycloakAccount;
import org.keycloak.adapters.springsecurity.authentication.DirectAccessGrantAuthenticationProvider;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.keycloak.adapters.springsecurity.userdetails.token.KeycloakUserDetailsAuthenticationToken;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.util.Assert;

import java.security.Principal;

/**
 * Provides a {@link DirectAccessGrantAuthenticationProvider} capable of
 * swapping the Keycloak principal with a {@link UserDetails user details} principal.
 *
 * <p>
 *     The supplied {@link UserDetailsService} is consulted using the Keycloak
 *     access token's principal attribute as the username.
 * </p>
 *
 * The original Keycloak principal is available from the {@link KeycloakAuthenticationToken}:
 *     <pre>
 *          KeycloakAuthenticationToken token = (KeycloakAuthenticationToken) SecurityContextHolder.getContext().getAuthentication());
 *          KeycloakAccount account = token.getAccount();
 *          Principal = account.getPrincipal();
 *     </pre>
 *
 * @author <a href="mailto:[email protected]">Scott Rossillo</a>
 *
 * @see UserDetailsService#loadUserByUsername
 * @see KeycloakUserDetailsAuthenticationToken
 */
public class DirectAccessGrantUserDetailsAuthenticationProvider extends DirectAccessGrantAuthenticationProvider
{

    private UserDetailsService userDetailsService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        KeycloakAuthenticationToken token = (KeycloakAuthenticationToken) super.authenticate(authentication);
        String username;
        UserDetails userDetails;

        if (token == null) {
            return null;
        }

        username = this.resolveUsername(token);
        userDetails = userDetailsService.loadUserByUsername(username);

        return new KeycloakUserDetailsAuthenticationToken(userDetails, token.getAccount(), token.getAuthorities());
    }

    /**
     * Returns the username from the given {@link KeycloakAuthenticationToken}. By default, this method
     * resolves the username from the token's {@link KeycloakPrincipal}'s name. This value can be controlled
     * via <code>keycloak.json</code>'s
     * <a href="http://docs.jboss.org/keycloak/docs/1.2.0.CR1/userguide/html/ch08.html#adapter-config"><code>principal-attribute</code></a>.
     * For more fine-grained username resolution, override this method.
     *
     * @param token the {@link KeycloakAuthenticationToken} from which to extract the username
     *
     * @return the username to use when loading a user from the this provider's {@link UserDetailsService}.
     *
     * @see UserDetailsService#loadUserByUsername
     * @see OidcKeycloakAccount#getPrincipal
     */
    protected String resolveUsername(KeycloakAuthenticationToken token) {

        Assert.notNull(token, "KeycloakAuthenticationToken required");
        Assert.notNull(token.getAccount(), "KeycloakAuthenticationToken.getAccount() cannot be return null");
        OidcKeycloakAccount account = token.getAccount();
        Principal principal = account.getPrincipal();

        return principal.getName();
    }

    @Required
    public void setUserDetailsService(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }
}