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

com.sun.enterprise.admin.util.AdminLoginModule Maven / Gradle / Ivy

There is a newer version: 8.0.0-JDK17-M7
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2012-2017 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://oss.oracle.com/licenses/CDDL+GPL-1.1
 * or LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package com.sun.enterprise.admin.util;

import com.sun.enterprise.config.serverbeans.Domain;
import com.sun.enterprise.config.serverbeans.SecureAdmin;
import com.sun.enterprise.config.serverbeans.SecureAdminPrincipal;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.inject.Inject;
import javax.security.auth.Subject;
import javax.security.auth.callback.*;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import org.glassfish.common.util.admin.AdminAuthenticator;
import org.glassfish.common.util.admin.AuthTokenManager;
import org.glassfish.common.util.admin.RestSessionManager;
import org.glassfish.hk2.api.PerLookup;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.internal.api.LocalPassword;
import org.jvnet.hk2.annotations.Service;

/**
 * Handles the non-username/password ways an admin user can authenticate.
 * 

* As specified by the LoginModule contract, the login method creates lists * of principals or credentials to be added to the Subject during commit. Only * if commit is invoked does the module actually add them to the Subject. * * @author tjquinn */ @Service @PerLookup public class AdminLoginModule implements LoginModule { private static final Logger logger = GenericAdminAuthenticator.ADMSEC_LOGGER; private static final Level PROGRESS_LEVEL = Level.FINE; @Inject private Domain domain; @Inject private AuthTokenManager authTokenManager; @Inject private LocalPassword localPassword; @Inject private RestSessionManager restSessionManager; private SecureAdmin secureAdmin = null; private boolean isAuthenticated; private Subject subject; private CallbackHandler callbackHandler; // Holds principals and credentials that should be added to the real // subject during commit, if it is ever invoked. private final Subject subjectToAssemble = new Subject(); private final UsernamePasswordAuthenticator usernamePasswordAuth = new UsernamePasswordAuthenticator(); private final PrincipalAuthenticator principalAuth = new PrincipalAuthenticator(); private final AdminIndicatorAuthenticator adminIndicatorAuth = new AdminIndicatorAuthenticator(); private final AdminTokenAuthenticator adminTokenAuth = new AdminTokenAuthenticator(); private final RemoteHostAuthenticator remoteHostAuth = new RemoteHostAuthenticator(); private final RestAdminAuthenticator restTokenAuthenticator = new RestAdminAuthenticator(); private List callbacks = new ArrayList(Arrays.asList( usernamePasswordAuth.nameCB, usernamePasswordAuth.pwCB, principalAuth.cb, adminIndicatorAuth.cb, adminTokenAuth.cb, remoteHostAuth.cb, restTokenAuthenticator.restTokenCB, restTokenAuthenticator.remoteAddrCB)); private List authenticators = new ArrayList(Arrays.asList( usernamePasswordAuth, principalAuth, adminIndicatorAuth, adminTokenAuth, restTokenAuthenticator)); @Override public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { if (callbackHandler instanceof AdminCallbackHandler) { ServiceLocator sl = ((AdminCallbackHandler) callbackHandler).getServiceLocator(); findServices(sl); } this.subject = subject; this.callbackHandler = callbackHandler; } private void findServices(final ServiceLocator sl) { domain = sl.getService(Domain.class); secureAdmin = domain.getSecureAdmin(); authTokenManager = sl.getService(AuthTokenManager.class); localPassword = sl.getService(LocalPassword.class); restSessionManager = sl.getService(RestSessionManager.class); } @Override public boolean login() throws LoginException { /* * Without a callback handler we cannot find out what we need about * the incoming request. */ if (callbackHandler == null) { throw new LoginException(Strings.get("secure.admin.noCallbackHandler")); } try { callbackHandler.handle(callbacks.toArray(new Callback[callbacks.size()])); } catch (Exception ex) { final LoginException lex = new LoginException(); lex.initCause(ex); throw lex; } /* * Make sure this login module has some way of authenticating this user. * Otherwise we don't need it to be invoked during commit or logout. */ isAuthenticated = false; for (AdminAuthenticator auth : authenticators) { isAuthenticated |= auth.identify(subjectToAssemble); } logger.log(PROGRESS_LEVEL, "login returning {0}", isAuthenticated); if ( ! isAuthenticated) { throw new LoginException(); } return isAuthenticated; } private void updateFromSubject(final Subject subjectToAddTo, final Subject subjectToAddFrom) { subjectToAddTo.getPrincipals().addAll(subjectToAddFrom.getPrincipals()); subjectToAddTo.getPrivateCredentials().addAll(subjectToAddFrom.getPrivateCredentials()); subjectToAddTo.getPublicCredentials().addAll(subjectToAddFrom.getPublicCredentials()); } @Override public boolean commit() throws LoginException { if ( ! isAuthenticated) { return false; } updateFromSubject(subject, subjectToAssemble); logger.log(PROGRESS_LEVEL, "commiting"); final Level dumpLevel = Level.FINER; if (logger.isLoggable(dumpLevel)) { logger.log(dumpLevel, "Following identity attached to subject: {0} principals, {1} private credentials, {2} public credentials", new Object[] {subjectToAssemble.getPrincipals().size(), subjectToAssemble.getPrivateCredentials().size(), subjectToAssemble.getPublicCredentials().size()}); for (Principal p : subjectToAssemble.getPrincipals()) { logger.log(dumpLevel, " principal: {0}", p.getName()); } for (Object c : subjectToAssemble.getPrivateCredentials()) { logger.log(dumpLevel, " private credential: {0}", c.toString()); } for (Object c : subjectToAssemble.getPublicCredentials()) { logger.log(dumpLevel, " public credential: {0}", c.toString()); } } return true; } @Override public boolean abort() throws LoginException { if ( ! isAuthenticated) { return false; } logger.log(PROGRESS_LEVEL, "aborting"); removeAddedInfo(); return true; } @Override public boolean logout() throws LoginException { logger.log(PROGRESS_LEVEL, "logging out"); removeAddedInfo(); return true; } private void removeAddedInfo() { subject.getPrincipals().removeAll(subjectToAssemble.getPrincipals()); subject.getPrivateCredentials().removeAll(subjectToAssemble.getPrivateCredentials()); subject.getPublicCredentials().removeAll(subjectToAssemble.getPublicCredentials()); } static class PrincipalCallback implements Callback { private Principal p; public void setPrincipal(final Principal p) { this.p = p; } public Principal getPrincipal() { return p; } } /* * If the admin client sent the unique domain identifier in a header then * that should mean the request came from another GlassFish server in this * domain. Make sure that the value, if present, matches the one in * this server's domain config. If they do not match then reject the * message - it came from a domain other than this server's. * * Note that we don't insist that every request have the domain identifier. For * example, requests from asadmin will not include the domain ID. But if * the domain ID is present in the request it needs to match the * configured ID. */ private static class SpecialAdminIndicatorChecker { private static enum Result { NOT_IN_REQUEST, MATCHED, MISMATCHED } private final SpecialAdminIndicatorChecker.Result _result; private SpecialAdminIndicatorChecker( final String actualIndicator, final String expectedIndicator, final String originHost) { final Level dumpLevel = Level.FINER; if (actualIndicator != null) { if (actualIndicator.equals(expectedIndicator)) { _result = SpecialAdminIndicatorChecker.Result.MATCHED; logger.log(dumpLevel, "Admin request contains expected domain ID"); } else { logger.log(Level.WARNING, AdminLoggerInfo.mForeignDomainID, new Object[] { originHost, actualIndicator, expectedIndicator}); _result = SpecialAdminIndicatorChecker.Result.MISMATCHED; } } else { logger.log(dumpLevel, "Admin request contains no domain ID; this is OK - continuing"); _result = SpecialAdminIndicatorChecker.Result.NOT_IN_REQUEST; } } private SpecialAdminIndicatorChecker.Result result() { return _result; } } abstract class Authenticator implements AdminAuthenticator { private final AuthenticatorType type; final Callback cb; Authenticator(final AuthenticatorType type, final Callback cb) { this.type = type; this.cb = cb; } @Override public List callbacks() { return new ArrayList(Arrays.asList(cb)); } @Override public AuthenticatorType type() { return type; } } abstract class TextAuthenticator extends Authenticator { final TextInputCallback textCB; TextAuthenticator(final AuthenticatorType type) { super(type, new TextInputCallback(type.name())); textCB = (TextInputCallback) cb; } } class PrincipalAuthenticator extends Authenticator { final private PrincipalCallback pcb; PrincipalAuthenticator() { super(AuthenticatorType.PRINCIPAL, new PrincipalCallback()); pcb = (PrincipalCallback) cb; } @Override public boolean identify(Subject subject) { /* * There are three ways certs can be attached to the request: * 1. A non-GlassFish client (a human) actually sent a cert. * * 2. One server is sending another server a request. In this case * the cert's DN should be among the secure-admin-principals. * * 3. The request came from the console and secure admin is enabled, * in which case (because the console runs in the DAS * and sends ReST requests over the net to the DAS) the request * will have both the DAS's cert and the user's username and password. */ final Principal p = pcb.getPrincipal(); if (p != null) { if (isPrincipalFromGlassFish(p) && usernamePasswordAuth.isActive()) { /* * This is the console case. Do not indicate that the client * is identifiable using an SSL cert. Instead rely on * the username/password authentication code path. */ logger.log(PROGRESS_LEVEL, "Detected console request - not adding SSL principal to the subject"); return false; } /* * In all other cases add the principal to the tentative subject. */ subject.getPrincipals().add(p); logger.log(PROGRESS_LEVEL, "Attaching Principal {0}", p.getName()); } return p != null; } private boolean isPrincipalFromGlassFish(final Principal p) { for (SecureAdminPrincipal sap : secureAdmin.getSecureAdminPrincipal()) { if (sap.getDn().equals(p.getName())) { return true; } } return false; } } class AdminIndicatorAuthenticator extends TextAuthenticator { AdminIndicatorAuthenticator() { super(AuthenticatorType.ADMIN_INDICATOR); } @Override public boolean identify(Subject subject) throws LoginException { if (secureAdmin == null) { return false; } final String providedIndicator = textCB.getText(); final SpecialAdminIndicatorChecker checker = new SpecialAdminIndicatorChecker( providedIndicator, secureAdmin.getSpecialAdminIndicator(), remoteHostAuth.textCB.getText()); if (checker.result() == SpecialAdminIndicatorChecker.Result.MISMATCHED) { throw new LoginException(); } /* * Either there was no special indicator or there was one and * it matched what we expect. */ if (checker.result() == SpecialAdminIndicatorChecker.Result.MATCHED) { /* * Add a principal indicating that this subject represents * another server in the domain and so we * can trust it completely, even if the request is remote and secure * admin is disabled. */ subject.getPrincipals().add(new AdminIndicatorPrincipal(providedIndicator)); return true; } return false; } } class AdminTokenAuthenticator extends TextAuthenticator { AdminTokenAuthenticator() { super(AuthenticatorType.ADMIN_TOKEN); } @Override public boolean identify(Subject subject) throws LoginException { if (authTokenManager == null) { return false; } Subject s = null; final String token = textCB.getText(); if (token != null) { s = authTokenManager.findToken(token); if (s != null) { /* * The token manager knows which Subject was effective when the token * was created. We add those to the lists we'll add if this module's * commit is invoked. */ logger.log(PROGRESS_LEVEL, "Recognized valid limited-use token"); updateFromSubject(subject, s); /* * Add an additional principal indicating that we trust this * subject to make remote requests. Otherwise we would * reject attempts to use a token from off-node, and that's * partly the whole point of tokens. */ subject.getPrincipals().add(new AdminTokenPrincipal(token)); } } return s != null; } } class UsernamePasswordAuthenticator extends Authenticator { final NameCallback nameCB = new NameCallback("username"); final PasswordCallback pwCB = new PasswordCallback("password", false); UsernamePasswordAuthenticator() { super(AuthenticatorType.USERNAME_PASSWORD, null); } boolean isActive() { return nameCB.getName() != null || pwCB.getPassword() != null; } @Override public boolean identify(final Subject subject) throws LoginException { /* * Note that this LoginModule does not authenticate the normal * username/password pairs. That's left to another one. This module * checks for the local password. */ if (localPassword == null) { return false; } final boolean result = localPassword.isLocalPassword(new String(pwCB.getPassword())); if (result) { subject.getPrincipals().add(new AdminLocalPasswordPrincipal()); logger.log(PROGRESS_LEVEL, "AdminLoginModule detected local password"); } return result; } @Override public List callbacks() { return new ArrayList(Arrays.asList(nameCB, pwCB)); } } class RemoteHostAuthenticator extends TextAuthenticator { RemoteHostAuthenticator() { super(AuthenticatorType.REMOTE_HOST); } @Override public boolean identify(final Subject subject) throws LoginException { return false; } } class RestAdminAuthenticator extends Authenticator { private TextInputCallback restTokenCB = new TextInputCallback(AdminAuthenticator.REST_TOKEN_NAME); private TextInputCallback remoteAddrCB = new TextInputCallback(AdminAuthenticator.REMOTE_ADDR_NAME); RestAdminAuthenticator() { super(AuthenticatorType.REST_TOKEN, null); } @Override public List callbacks() { return new ArrayList(Arrays.asList(cb, remoteAddrCB)); } @Override public boolean identify(final Subject subject) throws LoginException { if (restSessionManager == null) { return false; } boolean result = false; final String token = restTokenCB.getText(); final String remoteAddr = remoteAddrCB.getText(); if (token != null && remoteAddr != null) { final Subject s = restSessionManager.authenticate(token, remoteAddr); if (s != null) { result = true; updateFromSubject(subject, s); logger.log(PROGRESS_LEVEL, "Detected ReST token"); } } return result; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy