| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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.apache.jackrabbit.oak.spi.security.authentication; |
| |
| import java.io.IOException; |
| import java.security.Principal; |
| import java.security.PrivilegedActionException; |
| import java.security.PrivilegedExceptionAction; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| import javax.jcr.Credentials; |
| import javax.jcr.NoSuchWorkspaceException; |
| import javax.security.auth.DestroyFailedException; |
| import javax.security.auth.Destroyable; |
| import javax.security.auth.Subject; |
| import javax.security.auth.callback.Callback; |
| import javax.security.auth.callback.CallbackHandler; |
| import javax.security.auth.callback.UnsupportedCallbackException; |
| import javax.security.auth.login.LoginException; |
| import javax.security.auth.spi.LoginModule; |
| |
| import com.google.common.collect.ImmutableSet; |
| import org.apache.jackrabbit.api.security.user.UserManager; |
| import org.apache.jackrabbit.oak.api.AuthInfo; |
| import org.apache.jackrabbit.oak.api.ContentRepository; |
| import org.apache.jackrabbit.oak.api.ContentSession; |
| import org.apache.jackrabbit.oak.api.Root; |
| import org.apache.jackrabbit.oak.namepath.NamePathMapper; |
| import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters; |
| import org.apache.jackrabbit.oak.spi.security.SecurityProvider; |
| import org.apache.jackrabbit.oak.spi.security.authentication.callback.CredentialsCallback; |
| import org.apache.jackrabbit.oak.spi.security.authentication.callback.PrincipalProviderCallback; |
| import org.apache.jackrabbit.oak.spi.security.authentication.callback.RepositoryCallback; |
| import org.apache.jackrabbit.oak.spi.security.authentication.callback.UserManagerCallback; |
| import org.apache.jackrabbit.oak.spi.security.authentication.callback.WhiteboardCallback; |
| import org.apache.jackrabbit.oak.spi.security.principal.PrincipalConfiguration; |
| import org.apache.jackrabbit.oak.spi.security.principal.PrincipalProvider; |
| import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration; |
| import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import org.osgi.annotation.versioning.ProviderType; |
| |
| /** |
| * Abstract implementation of the {@link LoginModule} interface that can act |
| * as base class for login modules that aim to authenticate subjects against |
| * information stored in the content repository. |
| * <p> |
| * <h2>LoginModule Methods</h2> |
| * This base class provides a simple implementation for the following methods |
| * of the {@code LoginModule} interface: |
| * <p> |
| * <ul> |
| * <li>{@link LoginModule#initialize(Subject, CallbackHandler, Map, Map) Initialize}: |
| * Initialization of this abstract module sets the following protected instance |
| * fields: |
| * <ul> |
| * <li>subject: The subject to be authenticated,</li> |
| * <li>callbackHandler: The callback handler passed to the login module,</li> |
| * <li>shareState: The map used to share state information with other login modules,</li> |
| * <li>options: The configuration options of this login module as specified |
| * in the {@link javax.security.auth.login.Configuration}.</li> |
| * </ul> |
| * </li> |
| * <li>{@link LoginModule#logout() Logout}: |
| * If the authenticated subject is not empty this logout implementation |
| * attempts to clear both principals and public credentials and returns |
| * {@code true}.</li> |
| * <li>{@link LoginModule#abort() Abort}: Clears the state of this login |
| * module by setting all private instance variables created in phase 1 or 2 |
| * to {@code null}. Subclasses are in charge of releasing their own state |
| * information by either overriding {@link #clearState()}.</li> |
| * </ul> |
| * <p> |
| * <h2>Utility Methods</h2> |
| * The following methods are provided in addition: |
| * <p> |
| * <ul> |
| * <li>{@link #clearState()}: Clears all private state information that has |
| * be created during login. This method in called in {@link #abort()} and |
| * subclasses are expected to override this method.</li> |
| * <li>{@link #getSupportedCredentials()}: Abstract method used by |
| * {@link #getCredentials()} that reveals which credential implementations |
| * are supported by the {@code LoginModule}.</li> |
| * <li>{@link #getCredentials()}: Tries to retrieve valid (supported) |
| * Credentials in the following order: |
| * <ol> |
| * <li>using a {@link CredentialsCallback},</li> |
| * <li>looking for a {@link #SHARED_KEY_CREDENTIALS} entry in the shared |
| * state (see also {@link #getSharedCredentials()} and finally by</li> |
| * <li>searching for valid credentials in the subject.</li> |
| * </ol></li> |
| * <li>{@link #getSharedCredentials()}: This method returns credentials |
| * passed to the login module with the share state. The key to share credentials |
| * with a another module extending from this base class is |
| * {@link #SHARED_KEY_CREDENTIALS}. Note, that this method does not verify |
| * if the credentials provided by the shared state are |
| * {@link #getSupportedCredentials() supported}.</li> |
| * <li>{@link #getSharedLoginName()}: If the shared state contains an entry |
| * for {@link #SHARED_KEY_LOGIN_NAME} this method returns the value as login name.</li> |
| * <li>{@link #getSecurityProvider()}: Returns the configured security |
| * provider or {@code null}.</li> |
| * <li>{@link #getRoot()}: Provides access to the latest state of the |
| * repository in order to retrieve user or principal information required to |
| * authenticate the subject as well as to write back information during |
| * {@link #commit()}.</li> |
| * <li>{@link #getUserManager()}: Returns an instance of the configured |
| * {@link UserManager} or {@code null}.</li> |
| * <li>{@link #getPrincipalProvider()}: Returns an instance of the configured |
| * principal provider or {@code null}.</li> |
| * <li>{@link #getPrincipals(String)}: Utility that returns all principals |
| * associated with a given user id. This method might be be called after |
| * successful authentication in order to be able to populate the subject |
| * during {@link #commit()}. The implementation is a shortcut for calling |
| * {@link PrincipalProvider#getPrincipals(String) getPrincipals(String userId} |
| * on the provider exposed by {@link #getPrincipalProvider()}</li> |
| * </ul> |
| */ |
| @ProviderType |
| public abstract class AbstractLoginModule implements LoginModule { |
| |
| /** |
| * logger instance |
| */ |
| private static final Logger log = LoggerFactory.getLogger(AbstractLoginModule.class); |
| |
| /** |
| * Key of the sharedState entry referring to validated Credentials that is |
| * shared between multiple login modules. |
| */ |
| public static final String SHARED_KEY_CREDENTIALS = "org.apache.jackrabbit.credentials"; |
| |
| /** |
| * Key of the sharedState entry referring to a valid login ID that is shared |
| * between multiple login modules. |
| */ |
| public static final String SHARED_KEY_LOGIN_NAME = "javax.security.auth.login.name"; |
| |
| /** |
| * Key of the sharedState entry referring to public attributes that are shared |
| * between multiple login modules. |
| */ |
| public static final String SHARED_KEY_ATTRIBUTES = "javax.security.auth.login.attributes"; |
| |
| /** |
| * Key of the sharedState entry referring to pre authenticated login information that is shared |
| * between multiple login modules. |
| */ |
| public static final String SHARED_KEY_PRE_AUTH_LOGIN = PreAuthenticatedLogin.class.getName(); |
| |
| protected Subject subject; |
| protected CallbackHandler callbackHandler; |
| protected Map sharedState; |
| protected ConfigurationParameters options; |
| |
| private SecurityProvider securityProvider; |
| private LoginModuleMonitor loginModuleMonitor; |
| private Whiteboard whiteboard; |
| |
| private ContentSession systemSession; |
| private Root root; |
| |
| //--------------------------------------------------------< LoginModule >--- |
| @Override |
| public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) { |
| this.subject = subject; |
| this.callbackHandler = callbackHandler; |
| this.sharedState = sharedState; |
| this.options = (options == null) ? ConfigurationParameters.EMPTY : ConfigurationParameters.of(options); |
| } |
| |
| /** |
| * Besteffort default implementation of {@link LoginModule#logout()}, which removes all principals and all public |
| * credentials of type {@link Credentials} and {@link AuthInfo} from the subject. |
| * It will return {@code false}, if either principal set or credentials set is empty. |
| * |
| * Note, that this implementation is not able to only remove those principals/credentials that have been added |
| * by {@code this} very login module instance. Therefore subclasses should overwrite this method to provide a fully |
| * compliant solution of {@link #logout()}. They may however take advantage of {@link #logout(Set, Set)} |
| * in order to simplify the implementation of a logout that is compatible with the {@link LoginModule#logout()} |
| * contract incorporating the additional recommendations highlighted at |
| * <a href="https://docs.oracle.com/en/java/javase/13/security/java-authentication-and-authorization-service-jaas-loginmodule-developers-guide1.html#GUID-E9C5810B-ADB6-4454-869D-B269ECA8145F__LOGINMODULE.LOGOUTMETHOD-21144F6A">JAAS LoginModule Dev Guide</a> |
| * |
| * @return {@code true} if neither principals nor public credentials of type {@link Credentials} or {@link AuthInfo} |
| * stored in the {@link Subject} are empty; {@code false} otherwise |
| * @throws LoginException if the subject is readonly and destroying {@link Destroyable} credentials fails |
| * with {@link DestroyFailedException}. |
| */ |
| @Override |
| public boolean logout() throws LoginException { |
| boolean success = false; |
| Set<Object> creds = ImmutableSet.builder() |
| .addAll(subject.getPublicCredentials(Credentials.class)) |
| .addAll(subject.getPublicCredentials(AuthInfo.class)).build(); |
| if (!subject.getPrincipals().isEmpty() && !creds.isEmpty()) { |
| // clear subject if not readonly |
| if (!subject.isReadOnly()) { |
| subject.getPrincipals().clear(); |
| subject.getPublicCredentials().removeAll(creds); |
| } else { |
| destroyCredentials(creds); |
| } |
| success = true; |
| } |
| return success; |
| } |
| |
| @Override |
| public boolean abort() throws LoginException { |
| clearState(); |
| return true; |
| } |
| |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Clear state information that has been created during {@link #login()}. |
| */ |
| protected void clearState() { |
| securityProvider = null; |
| closeSystemSession(); |
| } |
| |
| /** |
| * Close the system session acquired upon {@link #getRoot()} and reset the associated root field. |
| * This method should be used instead of {@link #clearState()}, if {@link #login()} and {@link #commit()} were |
| * successfully completed but the system session is not needed for a successful {@link #logout()} |
| */ |
| protected void closeSystemSession() { |
| if (systemSession != null) { |
| try { |
| systemSession.close(); |
| } catch (IOException e) { |
| onError(); |
| log.error(e.getMessage(), e); |
| } |
| systemSession = null; |
| root = null; |
| } |
| } |
| |
| /** |
| * General logout-helper that will return {@code false} if both {@code credentials} and {@code principals} are {@code null}. |
| * Note, that this implementation will only throw {@code LoginException} if the {@code subject} is marked readonly |
| * and destroying {@link Destroyable} credentials fails. |
| * |
| * @param credentials The set of credentials extracted by this instance during login/commit to be removed from {@link Subject#getPublicCredentials()} |
| * @param principals A set of principals extracted by this instance during login/commit to be removed from {@link Subject#getPrincipals()} |
| * @return {@code true} if either the credential set or the principal set is not {@code null}, {@code false} otherwise. |
| * @throws LoginException If the subject is readonly and an error occurs while destroying any of the given credentials. |
| * @see <a href="https://docs.oracle.com/en/java/javase/13/security/java-authentication-and-authorization-service-jaas-loginmodule-developers-guide1.html#GUID-E9C5810B-ADB6-4454-869D-B269ECA8145F__LOGINMODULE.LOGOUTMETHOD-21144F6A">JAASLMDevGuide</a> |
| */ |
| protected boolean logout(@Nullable Set<Object> credentials, @Nullable Set<? extends Principal> principals) throws LoginException { |
| if (credentials != null || principals != null) { |
| if (!subject.isReadOnly()) { |
| if (credentials != null) { |
| subject.getPublicCredentials().removeAll(credentials); |
| } |
| if (principals != null) { |
| subject.getPrincipals().removeAll(principals); |
| } |
| } else if (credentials != null) { |
| destroyCredentials(credentials); |
| } |
| return true; |
| } else { |
| // this login module didn't add credentials/authinfo/principals to the subject upon commit |
| // -> logout of this LoginModule should be ignored |
| return false; |
| } |
| } |
| |
| private static void destroyCredentials(@NotNull Iterable<Object> credentials) throws LoginException { |
| for (Object cred : credentials) { |
| if (cred instanceof Destroyable) { |
| try { |
| ((Destroyable) cred).destroy(); |
| } catch (DestroyFailedException e) { |
| throw new LoginException(e.getMessage()); |
| } |
| } else { |
| log.debug("Unable to destroy credentials ({}) of read-only subject.", credentials.getClass().getName()); |
| } |
| } |
| } |
| |
| /** |
| * @return A set of supported credential classes. |
| */ |
| @NotNull |
| protected abstract Set<Class> getSupportedCredentials(); |
| |
| /** |
| * Tries to retrieve valid (supported) Credentials: |
| * <ol> |
| * <li>using a {@link CredentialsCallback},</li> |
| * <li>looking for a {@link #SHARED_KEY_CREDENTIALS} entry in the |
| * shared state (see also {@link #getSharedCredentials()} and finally by</li> |
| * <li>searching for valid credentials in the subject.</li> |
| * </ol> |
| * |
| * @return Valid (supported) credentials or {@code null}. |
| */ |
| @Nullable |
| protected Credentials getCredentials() { |
| Set<Class> supported = getSupportedCredentials(); |
| if (callbackHandler != null) { |
| log.debug("Login: retrieving Credentials using callback."); |
| try { |
| CredentialsCallback callback = new CredentialsCallback(); |
| callbackHandler.handle(new Callback[]{callback}); |
| Credentials creds = callback.getCredentials(); |
| if (creds != null && supported.contains(creds.getClass())) { |
| log.debug("Login: Credentials '{}' obtained from callback", creds); |
| return creds; |
| } else { |
| log.debug("Login: No supported credentials obtained from callback; trying shared state."); |
| } |
| } catch (UnsupportedCallbackException | IOException e) { |
| onError(); |
| log.error(e.getMessage(), e); |
| } |
| } |
| |
| Credentials creds = getSharedCredentials(); |
| if (creds != null && supported.contains(creds.getClass())) { |
| log.debug("Login: Credentials obtained from shared state."); |
| return creds; |
| } else { |
| log.debug("Login: No supported credentials found in shared state; looking for credentials in subject."); |
| for (Class clz : getSupportedCredentials()) { |
| Set<Credentials> cds = subject.getPublicCredentials(clz); |
| if (!cds.isEmpty()) { |
| log.debug("Login: Credentials found in subject."); |
| return cds.iterator().next(); |
| } |
| } |
| } |
| |
| log.debug("No credentials found."); |
| return null; |
| } |
| |
| /** |
| * @return The credentials passed to this login module with the shared state. |
| * @see #SHARED_KEY_CREDENTIALS |
| */ |
| @Nullable |
| protected Credentials getSharedCredentials() { |
| Credentials shared = null; |
| if (sharedState.containsKey(SHARED_KEY_CREDENTIALS)) { |
| Object sc = sharedState.get(SHARED_KEY_CREDENTIALS); |
| if (sc instanceof Credentials) { |
| shared = (Credentials) sc; |
| } else { |
| log.debug("Login: Invalid value for share state entry {}. Credentials expected.", SHARED_KEY_CREDENTIALS); |
| } |
| } |
| |
| return shared; |
| } |
| |
| /** |
| * @return The login name passed to this login module with the shared state. |
| * @see #SHARED_KEY_LOGIN_NAME |
| */ |
| @Nullable |
| protected String getSharedLoginName() { |
| if (sharedState.containsKey(SHARED_KEY_LOGIN_NAME)) { |
| return sharedState.get(SHARED_KEY_LOGIN_NAME).toString(); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * @return The pre authenticated login or {@code null} |
| * @see #SHARED_KEY_PRE_AUTH_LOGIN |
| */ |
| @Nullable |
| protected PreAuthenticatedLogin getSharedPreAuthLogin() { |
| Object login = sharedState.get(SHARED_KEY_PRE_AUTH_LOGIN); |
| if (login instanceof PreAuthenticatedLogin) { |
| return (PreAuthenticatedLogin) login; |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Tries to obtain the {@code SecurityProvider} object from the callback |
| * handler using a new SecurityProviderCallback and keeps the value as |
| * private field. If the callback handler isn't able to handle the |
| * SecurityProviderCallback this method returns {@code null}. |
| * |
| * @return The {@code SecurityProvider} associated with this |
| * {@code LoginModule} or {@code null}. |
| */ |
| @Nullable |
| protected SecurityProvider getSecurityProvider() { |
| if (securityProvider == null && callbackHandler != null) { |
| RepositoryCallback rcb = new RepositoryCallback(); |
| try { |
| callbackHandler.handle(new Callback[]{rcb}); |
| securityProvider = rcb.getSecurityProvider(); |
| } catch (IOException | UnsupportedCallbackException e) { |
| onError(); |
| log.error(e.getMessage(), e); |
| } |
| } |
| return securityProvider; |
| } |
| |
| /** |
| * Tries to obtain the {@code Whiteboard} object from the callback |
| * handler using a new WhiteboardCallback and keeps the value as |
| * private field. If the callback handler isn't able to handle the |
| * WhiteboardCallback this method returns {@code null}. |
| * |
| * @return The {@code Whiteboard} associated with this |
| * {@code LoginModule} or {@code null}. |
| */ |
| @Nullable |
| protected Whiteboard getWhiteboard() { |
| if (whiteboard == null && callbackHandler != null) { |
| WhiteboardCallback cb = new WhiteboardCallback(); |
| try { |
| callbackHandler.handle(new Callback[]{cb}); |
| whiteboard = cb.getWhiteboard(); |
| } catch (IOException | UnsupportedCallbackException e) { |
| onError(); |
| log.error(e.getMessage(), e); |
| } |
| } |
| return whiteboard; |
| } |
| |
| /** |
| * Tries to obtain a {@code Root} object from the callback handler using |
| * a new RepositoryCallback and keeps the value as private field. |
| * If the callback handler isn't able to handle the RepositoryCallback |
| * this method returns {@code null}. |
| * |
| * @return The {@code Root} associated with this {@code LoginModule} or |
| * {@code null}. |
| */ |
| @Nullable |
| protected Root getRoot() { |
| if (root == null && callbackHandler != null) { |
| try { |
| final RepositoryCallback rcb = new RepositoryCallback(); |
| callbackHandler.handle(new Callback[]{rcb}); |
| |
| final ContentRepository repository = rcb.getContentRepository(); |
| if (repository != null) { |
| systemSession = Subject.doAs(SystemSubject.INSTANCE, new PrivilegedExceptionAction<ContentSession>() { |
| @Override |
| public ContentSession run() throws LoginException, NoSuchWorkspaceException { |
| return repository.login(null, rcb.getWorkspaceName()); |
| } |
| }); |
| root = systemSession.getLatestRoot(); |
| } else { |
| log.error("Unable to retrieve the Root via RepositoryCallback; ContentRepository not available."); |
| } |
| } catch (IOException | UnsupportedCallbackException | PrivilegedActionException e) { |
| onError(); |
| log.error(e.getMessage(), e); |
| } |
| } |
| return root; |
| } |
| |
| /** |
| * Retrieves the {@link UserManager} that should be used to handle |
| * this authentication. If no user manager has been configure this |
| * method returns {@code null}. |
| * |
| * @return A instance of {@code UserManager} or {@code null}. |
| */ |
| @Nullable |
| protected UserManager getUserManager() { |
| UserManager userManager = null; |
| SecurityProvider sp = getSecurityProvider(); |
| Root r = getRoot(); |
| if (r != null && sp != null) { |
| UserConfiguration uc = securityProvider.getConfiguration(UserConfiguration.class); |
| userManager = uc.getUserManager(r, NamePathMapper.DEFAULT); |
| } |
| |
| if (userManager == null && callbackHandler != null) { |
| try { |
| UserManagerCallback userCallBack = new UserManagerCallback(); |
| callbackHandler.handle(new Callback[]{userCallBack}); |
| userManager = userCallBack.getUserManager(); |
| } catch (IOException | UnsupportedCallbackException e) { |
| onError(); |
| log.error(e.getMessage(), e); |
| } |
| } |
| |
| return userManager; |
| } |
| |
| /** |
| * Retrieves the {@link PrincipalProvider} that should be used to handle |
| * this authentication. If no principal provider has been configure this |
| * method returns {@code null}. |
| * |
| * @return A instance of {@code PrincipalProvider} or {@code null}. |
| */ |
| @Nullable |
| protected PrincipalProvider getPrincipalProvider() { |
| PrincipalProvider principalProvider = null; |
| SecurityProvider sp = getSecurityProvider(); |
| Root r = getRoot(); |
| if (r != null && sp != null) { |
| PrincipalConfiguration pc = sp.getConfiguration(PrincipalConfiguration.class); |
| principalProvider = pc.getPrincipalProvider(r, NamePathMapper.DEFAULT); |
| } |
| |
| if (principalProvider == null && callbackHandler != null) { |
| try { |
| PrincipalProviderCallback principalCallBack = new PrincipalProviderCallback(); |
| callbackHandler.handle(new Callback[]{principalCallBack}); |
| principalProvider = principalCallBack.getPrincipalProvider(); |
| } catch (IOException | UnsupportedCallbackException e) { |
| onError(); |
| log.error(e.getMessage(), e); |
| } |
| } |
| return principalProvider; |
| } |
| |
| /** |
| * Retrieves all principals associated with the specified {@code userId} for |
| * the configured principal provider. |
| * |
| * @param userId The id of the user. |
| * @return The set of principals associated with the given {@code userId}. |
| * @see #getPrincipalProvider() |
| */ |
| @NotNull |
| protected Set<? extends Principal> getPrincipals(@NotNull String userId) { |
| PrincipalProvider principalProvider = getPrincipalProvider(); |
| if (principalProvider == null) { |
| log.debug("Cannot retrieve principals. No principal provider configured."); |
| return Collections.emptySet(); |
| } else { |
| return principalProvider.getPrincipals(userId); |
| } |
| } |
| |
| @NotNull |
| protected Set<? extends Principal> getPrincipals(@NotNull Principal userPrincipal) { |
| PrincipalProvider principalProvider = getPrincipalProvider(); |
| if (principalProvider == null) { |
| log.debug("Cannot retrieve principals. No principal provider configured."); |
| return Collections.emptySet(); |
| } else { |
| Set<Principal> principals = new HashSet<>(); |
| principals.add(userPrincipal); |
| principals.addAll(principalProvider.getMembershipPrincipals(userPrincipal)); |
| return principals; |
| } |
| } |
| |
| protected static void setAuthInfo(@NotNull AuthInfo authInfo, @NotNull Subject subject) { |
| Set<AuthInfo> ais = subject.getPublicCredentials(AuthInfo.class); |
| if (!ais.isEmpty()) { |
| subject.getPublicCredentials().removeAll(ais); |
| } |
| subject.getPublicCredentials().add(authInfo); |
| } |
| |
| @NotNull |
| protected LoginModuleMonitor getLoginModuleMonitor() { |
| if (loginModuleMonitor == null && callbackHandler != null) { |
| RepositoryCallback rcb = new RepositoryCallback(); |
| try { |
| callbackHandler.handle(new Callback[] { rcb }); |
| loginModuleMonitor = rcb.getLoginModuleMonitor(); |
| } catch (IOException | UnsupportedCallbackException e) { |
| log.error(e.getMessage(), e); |
| } |
| } |
| if (loginModuleMonitor == null) { |
| loginModuleMonitor = LoginModuleMonitor.NOOP; |
| } |
| return loginModuleMonitor; |
| } |
| |
| protected void onError() { |
| getLoginModuleMonitor().loginError(); |
| } |
| } |