package com.thinkbiganalytics.auth.kylo; /*- * #%L * UserProvider Authentication * %% * Copyright (C) 2017 ThinkBig Analytics * %% * 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. * #L% */ import com.thinkbiganalytics.auth.jaas.AbstractLoginModule; import com.thinkbiganalytics.metadata.api.MetadataAccess; import com.thinkbiganalytics.metadata.api.user.User; import com.thinkbiganalytics.metadata.api.user.UserProvider; import org.springframework.security.crypto.password.PasswordEncoder; import java.util.Map; import java.util.Optional; import javax.annotation.Nonnull; import javax.security.auth.Subject; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.login.AccountLockedException; import javax.security.auth.login.AccountNotFoundException; import javax.security.auth.login.CredentialException; import javax.security.auth.login.LoginException; import javax.security.auth.spi.LoginModule; /** * A login module that authenticates users using the Kylo user store. By default * a user is authenticated only if a user with the provided username exits in * the store. It may also be configured to validate a provided password against the * one associated with the username in the store as well. */ public class KyloLoginModule extends AbstractLoginModule implements LoginModule { /** * Option for the {@link MetadataAccess} object */ public static final String METADATA_ACCESS = "metadataAccess"; /** * Option for the {@link PasswordEncoder} object */ public static final String PASSWORD_ENCODER = "passwordEncoder"; /** * Option for the {@link UserProvider} object */ public static final String USER_PROVIDER = "userProvider"; /** * Option that indicates whether password authentication is required */ public static final String REQUIRE_PASSWORD = "requirePassword"; /** * Metadata store */ private MetadataAccess metadata; /** * Password encoder */ private PasswordEncoder passwordEncoder; /** * Whether to required password validation for authentication */ private boolean requirePassword = false; /** * Metadata user provider */ private UserProvider userProvider; @Override public void initialize(@Nonnull final Subject subject, @Nonnull final CallbackHandler callbackHandler, @Nonnull final Map<String, ?> sharedState, @Nonnull final Map<String, ?> options) { super.initialize(subject, callbackHandler, sharedState, options); metadata = (MetadataAccess) getOption(METADATA_ACCESS).orElseThrow(() -> new IllegalArgumentException("The \"" + METADATA_ACCESS + "\" option is required")); passwordEncoder = (PasswordEncoder) getOption(PASSWORD_ENCODER).orElseThrow(() -> new IllegalArgumentException("The \"" + PASSWORD_ENCODER + "\" option is required")); userProvider = (UserProvider) getOption(USER_PROVIDER).orElseThrow(() -> new IllegalArgumentException("The \"" + USER_PROVIDER + "\" option is required")); requirePassword = (Boolean) getOption(REQUIRE_PASSWORD).orElse(false); } @Override protected boolean doLogin() throws Exception { // Get username and password final NameCallback nameCallback = new NameCallback("Username: "); final PasswordCallback passwordCallback = new PasswordCallback("Password: ", false); if (requirePassword) { handle(nameCallback, passwordCallback); } else { handle(nameCallback); } // Authenticate user metadata.read(() -> { Optional<User> user = userProvider.findUserBySystemName(nameCallback.getName()); if (user.isPresent()) { if (!user.get().isEnabled()) { throw new AccountLockedException("The account \"" + nameCallback.getName() + "\" is currently disabled"); } else if (requirePassword && ! passwordEncoder.matches(new String(passwordCallback.getPassword()), user.get().getPassword())) { throw new CredentialException("The username and/or password combination do not match"); } addPrincipal(user.get().getPrincipal()); addAllPrincipals(user.get().getAllGroupPrincipals()); } else { throw new AccountNotFoundException("No account exists with name name \"" + nameCallback.getName() + "\""); } }, MetadataAccess.SERVICE); return true; } @Override protected boolean doCommit() throws Exception { getSubject().getPrincipals().addAll(getAllPrincipals()); return true; } @Override protected boolean doAbort() throws Exception { return doLogout(); } @Override protected boolean doLogout() throws Exception { getSubject().getPrincipals().removeAll(getAllPrincipals()); return true; } /** * TODO */ @Nonnull private LoginException newLoginException(@Nonnull final Throwable cause) { final LoginException loginException = new LoginException("An unexpected login error occurred."); loginException.initCause(cause); return loginException; } }