feat(jdk8): move files to new folder to avoid resources compiled.
This commit is contained in:
570
jdkSrc/jdk8/com/sun/jmx/remote/security/FileLoginModule.java
Normal file
570
jdkSrc/jdk8/com/sun/jmx/remote/security/FileLoginModule.java
Normal file
@@ -0,0 +1,570 @@
|
||||
/*
|
||||
* Copyright (c) 2004, 2008, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code 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
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package com.sun.jmx.remote.security;
|
||||
|
||||
import com.sun.jmx.mbeanserver.GetPropertyAction;
|
||||
import com.sun.jmx.mbeanserver.Util;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FilePermission;
|
||||
import java.io.IOException;
|
||||
import java.security.AccessControlException;
|
||||
import java.security.AccessController;
|
||||
import java.util.Arrays;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.security.auth.*;
|
||||
import javax.security.auth.callback.*;
|
||||
import javax.security.auth.login.*;
|
||||
import javax.security.auth.spi.*;
|
||||
import javax.management.remote.JMXPrincipal;
|
||||
|
||||
import com.sun.jmx.remote.util.ClassLogger;
|
||||
import com.sun.jmx.remote.util.EnvHelp;
|
||||
import sun.management.jmxremote.ConnectorBootstrap;
|
||||
|
||||
/**
|
||||
* This {@link LoginModule} performs file-based authentication.
|
||||
*
|
||||
* <p> A supplied username and password is verified against the
|
||||
* corresponding user credentials stored in a designated password file.
|
||||
* If successful then a new {@link JMXPrincipal} is created with the
|
||||
* user's name and it is associated with the current {@link Subject}.
|
||||
* Such principals may be identified and granted management privileges in
|
||||
* the access control file for JMX remote management or in a Java security
|
||||
* policy.
|
||||
*
|
||||
* <p> The password file comprises a list of key-value pairs as specified in
|
||||
* {@link Properties}. The key represents a user's name and the value is its
|
||||
* associated cleartext password. By default, the following password file is
|
||||
* used:
|
||||
* <pre>
|
||||
* ${java.home}/lib/management/jmxremote.password
|
||||
* </pre>
|
||||
* A different password file can be specified via the <code>passwordFile</code>
|
||||
* configuration option.
|
||||
*
|
||||
* <p> This module recognizes the following <code>Configuration</code> options:
|
||||
* <dl>
|
||||
* <dt> <code>passwordFile</code> </dt>
|
||||
* <dd> the path to an alternative password file. It is used instead of
|
||||
* the default password file.</dd>
|
||||
*
|
||||
* <dt> <code>useFirstPass</code> </dt>
|
||||
* <dd> if <code>true</code>, this module retrieves the username and password
|
||||
* from the module's shared state, using "javax.security.auth.login.name"
|
||||
* and "javax.security.auth.login.password" as the respective keys. The
|
||||
* retrieved values are used for authentication. If authentication fails,
|
||||
* no attempt for a retry is made, and the failure is reported back to
|
||||
* the calling application.</dd>
|
||||
*
|
||||
* <dt> <code>tryFirstPass</code> </dt>
|
||||
* <dd> if <code>true</code>, this module retrieves the username and password
|
||||
* from the module's shared state, using "javax.security.auth.login.name"
|
||||
* and "javax.security.auth.login.password" as the respective keys. The
|
||||
* retrieved values are used for authentication. If authentication fails,
|
||||
* the module uses the CallbackHandler to retrieve a new username and
|
||||
* password, and another attempt to authenticate is made. If the
|
||||
* authentication fails, the failure is reported back to the calling
|
||||
* application.</dd>
|
||||
*
|
||||
* <dt> <code>storePass</code> </dt>
|
||||
* <dd> if <code>true</code>, this module stores the username and password
|
||||
* obtained from the CallbackHandler in the module's shared state, using
|
||||
* "javax.security.auth.login.name" and
|
||||
* "javax.security.auth.login.password" as the respective keys. This is
|
||||
* not performed if existing values already exist for the username and
|
||||
* password in the shared state, or if authentication fails.</dd>
|
||||
*
|
||||
* <dt> <code>clearPass</code> </dt>
|
||||
* <dd> if <code>true</code>, this module clears the username and password
|
||||
* stored in the module's shared state after both phases of authentication
|
||||
* (login and commit) have completed.</dd>
|
||||
* </dl>
|
||||
*/
|
||||
public class FileLoginModule implements LoginModule {
|
||||
|
||||
// Location of the default password file
|
||||
private static final String DEFAULT_PASSWORD_FILE_NAME =
|
||||
AccessController.doPrivileged(new GetPropertyAction("java.home")) +
|
||||
File.separatorChar + "lib" +
|
||||
File.separatorChar + "management" + File.separatorChar +
|
||||
ConnectorBootstrap.DefaultValues.PASSWORD_FILE_NAME;
|
||||
|
||||
// Key to retrieve the stored username
|
||||
private static final String USERNAME_KEY =
|
||||
"javax.security.auth.login.name";
|
||||
|
||||
// Key to retrieve the stored password
|
||||
private static final String PASSWORD_KEY =
|
||||
"javax.security.auth.login.password";
|
||||
|
||||
// Log messages
|
||||
private static final ClassLogger logger =
|
||||
new ClassLogger("javax.management.remote.misc", "FileLoginModule");
|
||||
|
||||
// Configurable options
|
||||
private boolean useFirstPass = false;
|
||||
private boolean tryFirstPass = false;
|
||||
private boolean storePass = false;
|
||||
private boolean clearPass = false;
|
||||
|
||||
// Authentication status
|
||||
private boolean succeeded = false;
|
||||
private boolean commitSucceeded = false;
|
||||
|
||||
// Supplied username and password
|
||||
private String username;
|
||||
private char[] password;
|
||||
private JMXPrincipal user;
|
||||
|
||||
// Initial state
|
||||
private Subject subject;
|
||||
private CallbackHandler callbackHandler;
|
||||
private Map<String, Object> sharedState;
|
||||
private Map<String, ?> options;
|
||||
private String passwordFile;
|
||||
private String passwordFileDisplayName;
|
||||
private boolean userSuppliedPasswordFile;
|
||||
private boolean hasJavaHomePermission;
|
||||
private Properties userCredentials;
|
||||
|
||||
/**
|
||||
* Initialize this <code>LoginModule</code>.
|
||||
*
|
||||
* @param subject the <code>Subject</code> to be authenticated.
|
||||
* @param callbackHandler a <code>CallbackHandler</code> to acquire the
|
||||
* user's name and password.
|
||||
* @param sharedState shared <code>LoginModule</code> state.
|
||||
* @param options options specified in the login
|
||||
* <code>Configuration</code> for this particular
|
||||
* <code>LoginModule</code>.
|
||||
*/
|
||||
public void initialize(Subject subject, CallbackHandler callbackHandler,
|
||||
Map<String,?> sharedState,
|
||||
Map<String,?> options)
|
||||
{
|
||||
|
||||
this.subject = subject;
|
||||
this.callbackHandler = callbackHandler;
|
||||
this.sharedState = Util.cast(sharedState);
|
||||
this.options = options;
|
||||
|
||||
// initialize any configured options
|
||||
tryFirstPass =
|
||||
"true".equalsIgnoreCase((String)options.get("tryFirstPass"));
|
||||
useFirstPass =
|
||||
"true".equalsIgnoreCase((String)options.get("useFirstPass"));
|
||||
storePass =
|
||||
"true".equalsIgnoreCase((String)options.get("storePass"));
|
||||
clearPass =
|
||||
"true".equalsIgnoreCase((String)options.get("clearPass"));
|
||||
|
||||
passwordFile = (String)options.get("passwordFile");
|
||||
passwordFileDisplayName = passwordFile;
|
||||
userSuppliedPasswordFile = true;
|
||||
|
||||
// set the location of the password file
|
||||
if (passwordFile == null) {
|
||||
passwordFile = DEFAULT_PASSWORD_FILE_NAME;
|
||||
userSuppliedPasswordFile = false;
|
||||
try {
|
||||
System.getProperty("java.home");
|
||||
hasJavaHomePermission = true;
|
||||
passwordFileDisplayName = passwordFile;
|
||||
} catch (SecurityException e) {
|
||||
hasJavaHomePermission = false;
|
||||
passwordFileDisplayName =
|
||||
ConnectorBootstrap.DefaultValues.PASSWORD_FILE_NAME;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin user authentication (Authentication Phase 1).
|
||||
*
|
||||
* <p> Acquire the user's name and password and verify them against
|
||||
* the corresponding credentials from the password file.
|
||||
*
|
||||
* @return true always, since this <code>LoginModule</code>
|
||||
* should not be ignored.
|
||||
* @exception FailedLoginException if the authentication fails.
|
||||
* @exception LoginException if this <code>LoginModule</code>
|
||||
* is unable to perform the authentication.
|
||||
*/
|
||||
public boolean login() throws LoginException {
|
||||
|
||||
try {
|
||||
loadPasswordFile();
|
||||
} catch (IOException ioe) {
|
||||
LoginException le = new LoginException(
|
||||
"Error: unable to load the password file: " +
|
||||
passwordFileDisplayName);
|
||||
throw EnvHelp.initCause(le, ioe);
|
||||
}
|
||||
|
||||
if (userCredentials == null) {
|
||||
throw new LoginException
|
||||
("Error: unable to locate the users' credentials.");
|
||||
}
|
||||
|
||||
if (logger.debugOn()) {
|
||||
logger.debug("login",
|
||||
"Using password file: " + passwordFileDisplayName);
|
||||
}
|
||||
|
||||
// attempt the authentication
|
||||
if (tryFirstPass) {
|
||||
|
||||
try {
|
||||
// attempt the authentication by getting the
|
||||
// username and password from shared state
|
||||
attemptAuthentication(true);
|
||||
|
||||
// authentication succeeded
|
||||
succeeded = true;
|
||||
if (logger.debugOn()) {
|
||||
logger.debug("login",
|
||||
"Authentication using cached password has succeeded");
|
||||
}
|
||||
return true;
|
||||
|
||||
} catch (LoginException le) {
|
||||
// authentication failed -- try again below by prompting
|
||||
cleanState();
|
||||
logger.debug("login",
|
||||
"Authentication using cached password has failed");
|
||||
}
|
||||
|
||||
} else if (useFirstPass) {
|
||||
|
||||
try {
|
||||
// attempt the authentication by getting the
|
||||
// username and password from shared state
|
||||
attemptAuthentication(true);
|
||||
|
||||
// authentication succeeded
|
||||
succeeded = true;
|
||||
if (logger.debugOn()) {
|
||||
logger.debug("login",
|
||||
"Authentication using cached password has succeeded");
|
||||
}
|
||||
return true;
|
||||
|
||||
} catch (LoginException le) {
|
||||
// authentication failed
|
||||
cleanState();
|
||||
logger.debug("login",
|
||||
"Authentication using cached password has failed");
|
||||
|
||||
throw le;
|
||||
}
|
||||
}
|
||||
|
||||
if (logger.debugOn()) {
|
||||
logger.debug("login", "Acquiring password");
|
||||
}
|
||||
|
||||
// attempt the authentication using the supplied username and password
|
||||
try {
|
||||
attemptAuthentication(false);
|
||||
|
||||
// authentication succeeded
|
||||
succeeded = true;
|
||||
if (logger.debugOn()) {
|
||||
logger.debug("login", "Authentication has succeeded");
|
||||
}
|
||||
return true;
|
||||
|
||||
} catch (LoginException le) {
|
||||
cleanState();
|
||||
logger.debug("login", "Authentication has failed");
|
||||
|
||||
throw le;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete user authentication (Authentication Phase 2).
|
||||
*
|
||||
* <p> This method is called if the LoginContext's
|
||||
* overall authentication has succeeded
|
||||
* (all the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL
|
||||
* LoginModules have succeeded).
|
||||
*
|
||||
* <p> If this LoginModule's own authentication attempt
|
||||
* succeeded (checked by retrieving the private state saved by the
|
||||
* <code>login</code> method), then this method associates a
|
||||
* <code>JMXPrincipal</code> with the <code>Subject</code> located in the
|
||||
* <code>LoginModule</code>. If this LoginModule's own
|
||||
* authentication attempted failed, then this method removes
|
||||
* any state that was originally saved.
|
||||
*
|
||||
* @exception LoginException if the commit fails
|
||||
* @return true if this LoginModule's own login and commit
|
||||
* attempts succeeded, or false otherwise.
|
||||
*/
|
||||
public boolean commit() throws LoginException {
|
||||
|
||||
if (succeeded == false) {
|
||||
return false;
|
||||
} else {
|
||||
if (subject.isReadOnly()) {
|
||||
cleanState();
|
||||
throw new LoginException("Subject is read-only");
|
||||
}
|
||||
// add Principals to the Subject
|
||||
if (!subject.getPrincipals().contains(user)) {
|
||||
subject.getPrincipals().add(user);
|
||||
}
|
||||
|
||||
if (logger.debugOn()) {
|
||||
logger.debug("commit",
|
||||
"Authentication has completed successfully");
|
||||
}
|
||||
}
|
||||
// in any case, clean out state
|
||||
cleanState();
|
||||
commitSucceeded = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abort user authentication (Authentication Phase 2).
|
||||
*
|
||||
* <p> This method is called if the LoginContext's overall authentication
|
||||
* failed (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL
|
||||
* LoginModules did not succeed).
|
||||
*
|
||||
* <p> If this LoginModule's own authentication attempt
|
||||
* succeeded (checked by retrieving the private state saved by the
|
||||
* <code>login</code> and <code>commit</code> methods),
|
||||
* then this method cleans up any state that was originally saved.
|
||||
*
|
||||
* @exception LoginException if the abort fails.
|
||||
* @return false if this LoginModule's own login and/or commit attempts
|
||||
* failed, and true otherwise.
|
||||
*/
|
||||
public boolean abort() throws LoginException {
|
||||
|
||||
if (logger.debugOn()) {
|
||||
logger.debug("abort",
|
||||
"Authentication has not completed successfully");
|
||||
}
|
||||
|
||||
if (succeeded == false) {
|
||||
return false;
|
||||
} else if (succeeded == true && commitSucceeded == false) {
|
||||
|
||||
// Clean out state
|
||||
succeeded = false;
|
||||
cleanState();
|
||||
user = null;
|
||||
} else {
|
||||
// overall authentication succeeded and commit succeeded,
|
||||
// but someone else's commit failed
|
||||
logout();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout a user.
|
||||
*
|
||||
* <p> This method removes the Principals
|
||||
* that were added by the <code>commit</code> method.
|
||||
*
|
||||
* @exception LoginException if the logout fails.
|
||||
* @return true in all cases since this <code>LoginModule</code>
|
||||
* should not be ignored.
|
||||
*/
|
||||
public boolean logout() throws LoginException {
|
||||
if (subject.isReadOnly()) {
|
||||
cleanState();
|
||||
throw new LoginException ("Subject is read-only");
|
||||
}
|
||||
subject.getPrincipals().remove(user);
|
||||
|
||||
// clean out state
|
||||
cleanState();
|
||||
succeeded = false;
|
||||
commitSucceeded = false;
|
||||
user = null;
|
||||
|
||||
if (logger.debugOn()) {
|
||||
logger.debug("logout", "Subject is being logged out");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt authentication
|
||||
*
|
||||
* @param usePasswdFromSharedState a flag to tell this method whether
|
||||
* to retrieve the password from the sharedState.
|
||||
*/
|
||||
@SuppressWarnings("unchecked") // sharedState used as Map<String,Object>
|
||||
private void attemptAuthentication(boolean usePasswdFromSharedState)
|
||||
throws LoginException {
|
||||
|
||||
// get the username and password
|
||||
getUsernamePassword(usePasswdFromSharedState);
|
||||
|
||||
String localPassword;
|
||||
|
||||
// userCredentials is initialized in login()
|
||||
if (((localPassword = userCredentials.getProperty(username)) == null) ||
|
||||
(! localPassword.equals(new String(password)))) {
|
||||
|
||||
// username not found or passwords do not match
|
||||
if (logger.debugOn()) {
|
||||
logger.debug("login", "Invalid username or password");
|
||||
}
|
||||
throw new FailedLoginException("Invalid username or password");
|
||||
}
|
||||
|
||||
// Save the username and password in the shared state
|
||||
// only if authentication succeeded
|
||||
if (storePass &&
|
||||
!sharedState.containsKey(USERNAME_KEY) &&
|
||||
!sharedState.containsKey(PASSWORD_KEY)) {
|
||||
sharedState.put(USERNAME_KEY, username);
|
||||
sharedState.put(PASSWORD_KEY, password);
|
||||
}
|
||||
|
||||
// Create a new user principal
|
||||
user = new JMXPrincipal(username);
|
||||
|
||||
if (logger.debugOn()) {
|
||||
logger.debug("login",
|
||||
"User '" + username + "' successfully validated");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the password file.
|
||||
*/
|
||||
private void loadPasswordFile() throws IOException {
|
||||
FileInputStream fis;
|
||||
try {
|
||||
fis = new FileInputStream(passwordFile);
|
||||
} catch (SecurityException e) {
|
||||
if (userSuppliedPasswordFile || hasJavaHomePermission) {
|
||||
throw e;
|
||||
} else {
|
||||
final FilePermission fp =
|
||||
new FilePermission(passwordFileDisplayName, "read");
|
||||
AccessControlException ace = new AccessControlException(
|
||||
"access denied " + fp.toString());
|
||||
ace.setStackTrace(e.getStackTrace());
|
||||
throw ace;
|
||||
}
|
||||
}
|
||||
try {
|
||||
final BufferedInputStream bis = new BufferedInputStream(fis);
|
||||
try {
|
||||
userCredentials = new Properties();
|
||||
userCredentials.load(bis);
|
||||
} finally {
|
||||
bis.close();
|
||||
}
|
||||
} finally {
|
||||
fis.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the username and password.
|
||||
* This method does not return any value.
|
||||
* Instead, it sets global name and password variables.
|
||||
*
|
||||
* <p> Also note that this method will set the username and password
|
||||
* values in the shared state in case subsequent LoginModules
|
||||
* want to use them via use/tryFirstPass.
|
||||
*
|
||||
* @param usePasswdFromSharedState boolean that tells this method whether
|
||||
* to retrieve the password from the sharedState.
|
||||
*/
|
||||
private void getUsernamePassword(boolean usePasswdFromSharedState)
|
||||
throws LoginException {
|
||||
|
||||
if (usePasswdFromSharedState) {
|
||||
// use the password saved by the first module in the stack
|
||||
username = (String)sharedState.get(USERNAME_KEY);
|
||||
password = (char[])sharedState.get(PASSWORD_KEY);
|
||||
return;
|
||||
}
|
||||
|
||||
// acquire username and password
|
||||
if (callbackHandler == null)
|
||||
throw new LoginException("Error: no CallbackHandler available " +
|
||||
"to garner authentication information from the user");
|
||||
|
||||
Callback[] callbacks = new Callback[2];
|
||||
callbacks[0] = new NameCallback("username");
|
||||
callbacks[1] = new PasswordCallback("password", false);
|
||||
|
||||
try {
|
||||
callbackHandler.handle(callbacks);
|
||||
username = ((NameCallback)callbacks[0]).getName();
|
||||
char[] tmpPassword = ((PasswordCallback)callbacks[1]).getPassword();
|
||||
password = new char[tmpPassword.length];
|
||||
System.arraycopy(tmpPassword, 0,
|
||||
password, 0, tmpPassword.length);
|
||||
((PasswordCallback)callbacks[1]).clearPassword();
|
||||
|
||||
} catch (IOException ioe) {
|
||||
LoginException le = new LoginException(ioe.toString());
|
||||
throw EnvHelp.initCause(le, ioe);
|
||||
} catch (UnsupportedCallbackException uce) {
|
||||
LoginException le = new LoginException(
|
||||
"Error: " + uce.getCallback().toString() +
|
||||
" not available to garner authentication " +
|
||||
"information from the user");
|
||||
throw EnvHelp.initCause(le, uce);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean out state because of a failed authentication attempt
|
||||
*/
|
||||
private void cleanState() {
|
||||
username = null;
|
||||
if (password != null) {
|
||||
Arrays.fill(password, ' ');
|
||||
password = null;
|
||||
}
|
||||
|
||||
if (clearPass) {
|
||||
sharedState.remove(USERNAME_KEY);
|
||||
sharedState.remove(PASSWORD_KEY);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
/*
|
||||
* Copyright (c) 2004, 2008, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code 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
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package com.sun.jmx.remote.security;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.AccessController;
|
||||
import java.security.Principal;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.security.PrivilegedActionException;
|
||||
import java.security.PrivilegedExceptionAction;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import javax.management.remote.JMXPrincipal;
|
||||
import javax.management.remote.JMXAuthenticator;
|
||||
import javax.security.auth.AuthPermission;
|
||||
import javax.security.auth.Subject;
|
||||
import javax.security.auth.callback.*;
|
||||
import javax.security.auth.login.AppConfigurationEntry;
|
||||
import javax.security.auth.login.Configuration;
|
||||
import javax.security.auth.login.LoginContext;
|
||||
import javax.security.auth.login.LoginException;
|
||||
import javax.security.auth.spi.LoginModule;
|
||||
import com.sun.jmx.remote.util.ClassLogger;
|
||||
import com.sun.jmx.remote.util.EnvHelp;
|
||||
|
||||
/**
|
||||
* <p>This class represents a
|
||||
* <a href="{@docRoot}/../guide/security/jaas/JAASRefGuide.html">JAAS</a>
|
||||
* based implementation of the {@link JMXAuthenticator} interface.</p>
|
||||
*
|
||||
* <p>Authentication is performed by passing the supplied user's credentials
|
||||
* to one or more authentication mechanisms ({@link LoginModule}) for
|
||||
* verification. An authentication mechanism acquires the user's credentials
|
||||
* by calling {@link NameCallback} and/or {@link PasswordCallback}.
|
||||
* If authentication is successful then an authenticated {@link Subject}
|
||||
* filled in with a {@link Principal} is returned. Authorization checks
|
||||
* will then be performed based on this <code>Subject</code>.</p>
|
||||
*
|
||||
* <p>By default, a single file-based authentication mechanism
|
||||
* {@link FileLoginModule} is configured (<code>FileLoginConfig</code>).</p>
|
||||
*
|
||||
* <p>To override the default configuration use the
|
||||
* <code>com.sun.management.jmxremote.login.config</code> management property
|
||||
* described in the JRE/lib/management/management.properties file.
|
||||
* Set this property to the name of a JAAS configuration entry and ensure that
|
||||
* the entry is loaded by the installed {@link Configuration}. In addition,
|
||||
* ensure that the authentication mechanisms specified in the entry acquire
|
||||
* the user's credentials by calling {@link NameCallback} and
|
||||
* {@link PasswordCallback} and that they return a {@link Subject} filled-in
|
||||
* with a {@link Principal}, for those users that are successfully
|
||||
* authenticated.</p>
|
||||
*/
|
||||
public final class JMXPluggableAuthenticator implements JMXAuthenticator {
|
||||
|
||||
/**
|
||||
* Creates an instance of <code>JMXPluggableAuthenticator</code>
|
||||
* and initializes it with a {@link LoginContext}.
|
||||
*
|
||||
* @param env the environment containing configuration properties for the
|
||||
* authenticator. Can be null, which is equivalent to an empty
|
||||
* Map.
|
||||
* @exception SecurityException if the authentication mechanism cannot be
|
||||
* initialized.
|
||||
*/
|
||||
public JMXPluggableAuthenticator(Map<?, ?> env) {
|
||||
|
||||
String loginConfigName = null;
|
||||
String passwordFile = null;
|
||||
|
||||
if (env != null) {
|
||||
loginConfigName = (String) env.get(LOGIN_CONFIG_PROP);
|
||||
passwordFile = (String) env.get(PASSWORD_FILE_PROP);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
if (loginConfigName != null) {
|
||||
// use the supplied JAAS login configuration
|
||||
loginContext =
|
||||
new LoginContext(loginConfigName, new JMXCallbackHandler());
|
||||
|
||||
} else {
|
||||
// use the default JAAS login configuration (file-based)
|
||||
SecurityManager sm = System.getSecurityManager();
|
||||
if (sm != null) {
|
||||
sm.checkPermission(
|
||||
new AuthPermission("createLoginContext." +
|
||||
LOGIN_CONFIG_NAME));
|
||||
}
|
||||
|
||||
final String pf = passwordFile;
|
||||
try {
|
||||
loginContext = AccessController.doPrivileged(
|
||||
new PrivilegedExceptionAction<LoginContext>() {
|
||||
public LoginContext run() throws LoginException {
|
||||
return new LoginContext(
|
||||
LOGIN_CONFIG_NAME,
|
||||
null,
|
||||
new JMXCallbackHandler(),
|
||||
new FileLoginConfig(pf));
|
||||
}
|
||||
});
|
||||
} catch (PrivilegedActionException pae) {
|
||||
throw (LoginException) pae.getException();
|
||||
}
|
||||
}
|
||||
|
||||
} catch (LoginException le) {
|
||||
authenticationFailure("authenticate", le);
|
||||
|
||||
} catch (SecurityException se) {
|
||||
authenticationFailure("authenticate", se);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the <code>MBeanServerConnection</code> client
|
||||
* with the given client credentials.
|
||||
*
|
||||
* @param credentials the user-defined credentials to be passed in
|
||||
* to the server in order to authenticate the user before creating
|
||||
* the <code>MBeanServerConnection</code>. This parameter must
|
||||
* be a two-element <code>String[]</code> containing the client's
|
||||
* username and password in that order.
|
||||
*
|
||||
* @return the authenticated subject containing a
|
||||
* <code>JMXPrincipal(username)</code>.
|
||||
*
|
||||
* @exception SecurityException if the server cannot authenticate the user
|
||||
* with the provided credentials.
|
||||
*/
|
||||
public Subject authenticate(Object credentials) {
|
||||
// Verify that credentials is of type String[].
|
||||
//
|
||||
if (!(credentials instanceof String[])) {
|
||||
// Special case for null so we get a more informative message
|
||||
if (credentials == null)
|
||||
authenticationFailure("authenticate", "Credentials required");
|
||||
|
||||
final String message =
|
||||
"Credentials should be String[] instead of " +
|
||||
credentials.getClass().getName();
|
||||
authenticationFailure("authenticate", message);
|
||||
}
|
||||
// Verify that the array contains two elements.
|
||||
//
|
||||
final String[] aCredentials = (String[]) credentials;
|
||||
if (aCredentials.length != 2) {
|
||||
final String message =
|
||||
"Credentials should have 2 elements not " +
|
||||
aCredentials.length;
|
||||
authenticationFailure("authenticate", message);
|
||||
}
|
||||
// Verify that username exists and the associated
|
||||
// password matches the one supplied by the client.
|
||||
//
|
||||
username = aCredentials[0];
|
||||
password = aCredentials[1];
|
||||
if (username == null || password == null) {
|
||||
final String message = "Username or password is null";
|
||||
authenticationFailure("authenticate", message);
|
||||
}
|
||||
|
||||
// Perform authentication
|
||||
try {
|
||||
loginContext.login();
|
||||
final Subject subject = loginContext.getSubject();
|
||||
AccessController.doPrivileged(new PrivilegedAction<Void>() {
|
||||
public Void run() {
|
||||
subject.setReadOnly();
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
return subject;
|
||||
|
||||
} catch (LoginException le) {
|
||||
authenticationFailure("authenticate", le);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void authenticationFailure(String method, String message)
|
||||
throws SecurityException {
|
||||
final String msg = "Authentication failed! " + message;
|
||||
final SecurityException e = new SecurityException(msg);
|
||||
logException(method, msg, e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
private static void authenticationFailure(String method,
|
||||
Exception exception)
|
||||
throws SecurityException {
|
||||
String msg;
|
||||
SecurityException se;
|
||||
if (exception instanceof SecurityException) {
|
||||
msg = exception.getMessage();
|
||||
se = (SecurityException) exception;
|
||||
} else {
|
||||
msg = "Authentication failed! " + exception.getMessage();
|
||||
final SecurityException e = new SecurityException(msg);
|
||||
EnvHelp.initCause(e, exception);
|
||||
se = e;
|
||||
}
|
||||
logException(method, msg, se);
|
||||
throw se;
|
||||
}
|
||||
|
||||
private static void logException(String method,
|
||||
String message,
|
||||
Exception e) {
|
||||
if (logger.traceOn()) {
|
||||
logger.trace(method, message);
|
||||
}
|
||||
if (logger.debugOn()) {
|
||||
logger.debug(method, e);
|
||||
}
|
||||
}
|
||||
|
||||
private LoginContext loginContext;
|
||||
private String username;
|
||||
private String password;
|
||||
private static final String LOGIN_CONFIG_PROP =
|
||||
"jmx.remote.x.login.config";
|
||||
private static final String LOGIN_CONFIG_NAME = "JMXPluggableAuthenticator";
|
||||
private static final String PASSWORD_FILE_PROP =
|
||||
"jmx.remote.x.password.file";
|
||||
private static final ClassLogger logger =
|
||||
new ClassLogger("javax.management.remote.misc", LOGIN_CONFIG_NAME);
|
||||
|
||||
/**
|
||||
* This callback handler supplies the username and password (which was
|
||||
* originally supplied by the JMX user) to the JAAS login module performing
|
||||
* the authentication. No interactive user prompting is required because the
|
||||
* credentials are already available to this class (via its enclosing class).
|
||||
*/
|
||||
private final class JMXCallbackHandler implements CallbackHandler {
|
||||
|
||||
/**
|
||||
* Sets the username and password in the appropriate Callback object.
|
||||
*/
|
||||
public void handle(Callback[] callbacks)
|
||||
throws IOException, UnsupportedCallbackException {
|
||||
|
||||
for (int i = 0; i < callbacks.length; i++) {
|
||||
if (callbacks[i] instanceof NameCallback) {
|
||||
((NameCallback)callbacks[i]).setName(username);
|
||||
|
||||
} else if (callbacks[i] instanceof PasswordCallback) {
|
||||
((PasswordCallback)callbacks[i])
|
||||
.setPassword(password.toCharArray());
|
||||
|
||||
} else {
|
||||
throw new UnsupportedCallbackException
|
||||
(callbacks[i], "Unrecognized Callback");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class defines the JAAS configuration for file-based authentication.
|
||||
* It is equivalent to the following textual configuration entry:
|
||||
* <pre>
|
||||
* JMXPluggableAuthenticator {
|
||||
* com.sun.jmx.remote.security.FileLoginModule required;
|
||||
* };
|
||||
* </pre>
|
||||
*/
|
||||
private static class FileLoginConfig extends Configuration {
|
||||
|
||||
// The JAAS configuration for file-based authentication
|
||||
private AppConfigurationEntry[] entries;
|
||||
|
||||
// The classname of the login module for file-based authentication
|
||||
private static final String FILE_LOGIN_MODULE =
|
||||
FileLoginModule.class.getName();
|
||||
|
||||
// The option that identifies the password file to use
|
||||
private static final String PASSWORD_FILE_OPTION = "passwordFile";
|
||||
|
||||
/**
|
||||
* Creates an instance of <code>FileLoginConfig</code>
|
||||
*
|
||||
* @param passwordFile A filepath that identifies the password file to use.
|
||||
* If null then the default password file is used.
|
||||
*/
|
||||
public FileLoginConfig(String passwordFile) {
|
||||
|
||||
Map<String, String> options;
|
||||
if (passwordFile != null) {
|
||||
options = new HashMap<String, String>(1);
|
||||
options.put(PASSWORD_FILE_OPTION, passwordFile);
|
||||
} else {
|
||||
options = Collections.emptyMap();
|
||||
}
|
||||
|
||||
entries = new AppConfigurationEntry[] {
|
||||
new AppConfigurationEntry(FILE_LOGIN_MODULE,
|
||||
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
|
||||
options)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the JAAS configuration for file-based authentication
|
||||
*/
|
||||
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
|
||||
|
||||
return name.equals(LOGIN_CONFIG_NAME) ? entries : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the configuration.
|
||||
*/
|
||||
public void refresh() {
|
||||
// the configuration is fixed
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (c) 2003, 2005, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code 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
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package com.sun.jmx.remote.security;
|
||||
|
||||
import java.security.AccessControlContext;
|
||||
import java.security.AccessController;
|
||||
import java.security.CodeSource;
|
||||
import java.security.Permissions;
|
||||
import java.security.ProtectionDomain;
|
||||
import javax.security.auth.Subject;
|
||||
import javax.security.auth.SubjectDomainCombiner;
|
||||
|
||||
/**
|
||||
* <p>This class represents an extension to the {@link SubjectDomainCombiner}
|
||||
* and is used to add a new {@link ProtectionDomain}, comprised of a null
|
||||
* codesource/signers and an empty permission set, to the access control
|
||||
* context with which this combiner is combined.</p>
|
||||
*
|
||||
* <p>When the {@link #combine} method is called the {@link ProtectionDomain}
|
||||
* is augmented with the permissions granted to the set of principals present
|
||||
* in the supplied {@link Subject}.</p>
|
||||
*/
|
||||
public class JMXSubjectDomainCombiner extends SubjectDomainCombiner {
|
||||
|
||||
public JMXSubjectDomainCombiner(Subject s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
public ProtectionDomain[] combine(ProtectionDomain[] current,
|
||||
ProtectionDomain[] assigned) {
|
||||
// Add a new ProtectionDomain with the null codesource/signers, and
|
||||
// the empty permission set, to the end of the array containing the
|
||||
// 'current' protections domains, i.e. the ones that will be augmented
|
||||
// with the permissions granted to the set of principals present in
|
||||
// the supplied subject.
|
||||
//
|
||||
ProtectionDomain[] newCurrent;
|
||||
if (current == null || current.length == 0) {
|
||||
newCurrent = new ProtectionDomain[1];
|
||||
newCurrent[0] = pdNoPerms;
|
||||
} else {
|
||||
newCurrent = new ProtectionDomain[current.length + 1];
|
||||
for (int i = 0; i < current.length; i++) {
|
||||
newCurrent[i] = current[i];
|
||||
}
|
||||
newCurrent[current.length] = pdNoPerms;
|
||||
}
|
||||
return super.combine(newCurrent, assigned);
|
||||
}
|
||||
|
||||
/**
|
||||
* A null CodeSource.
|
||||
*/
|
||||
private static final CodeSource nullCodeSource =
|
||||
new CodeSource(null, (java.security.cert.Certificate[]) null);
|
||||
|
||||
/**
|
||||
* A ProtectionDomain with a null CodeSource and an empty permission set.
|
||||
*/
|
||||
private static final ProtectionDomain pdNoPerms =
|
||||
new ProtectionDomain(nullCodeSource, new Permissions(), null, null);
|
||||
|
||||
/**
|
||||
* Get the current AccessControlContext combined with the supplied subject.
|
||||
*/
|
||||
public static AccessControlContext getContext(Subject subject) {
|
||||
return new AccessControlContext(AccessController.getContext(),
|
||||
new JMXSubjectDomainCombiner(subject));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the AccessControlContext of the domain combiner created with
|
||||
* the supplied subject, i.e. an AccessControlContext with the domain
|
||||
* combiner created with the supplied subject and where the caller's
|
||||
* context has been removed.
|
||||
*/
|
||||
public static AccessControlContext
|
||||
getDomainCombinerContext(Subject subject) {
|
||||
return new AccessControlContext(
|
||||
new AccessControlContext(new ProtectionDomain[0]),
|
||||
new JMXSubjectDomainCombiner(subject));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,665 @@
|
||||
/*
|
||||
* Copyright (c) 2003, 2006, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code 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
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package com.sun.jmx.remote.security;
|
||||
|
||||
import com.sun.jmx.mbeanserver.GetPropertyAction;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.security.AccessController;
|
||||
import java.util.Set;
|
||||
import javax.management.Attribute;
|
||||
import javax.management.AttributeList;
|
||||
import javax.management.AttributeNotFoundException;
|
||||
import javax.management.InstanceNotFoundException;
|
||||
import javax.management.InstanceAlreadyExistsException;
|
||||
import javax.management.IntrospectionException;
|
||||
import javax.management.InvalidAttributeValueException;
|
||||
import javax.management.ListenerNotFoundException;
|
||||
import javax.management.MBeanException;
|
||||
import javax.management.MBeanInfo;
|
||||
import javax.management.MBeanRegistrationException;
|
||||
import javax.management.MBeanServer;
|
||||
import javax.management.NotCompliantMBeanException;
|
||||
import javax.management.NotificationFilter;
|
||||
import javax.management.NotificationListener;
|
||||
import javax.management.ObjectInstance;
|
||||
import javax.management.ObjectName;
|
||||
import javax.management.OperationsException;
|
||||
import javax.management.QueryExp;
|
||||
import javax.management.ReflectionException;
|
||||
import javax.management.loading.ClassLoaderRepository;
|
||||
import javax.management.remote.MBeanServerForwarder;
|
||||
|
||||
/**
|
||||
* <p>An object of this class implements the MBeanServer interface
|
||||
* and, for each of its methods, calls an appropriate checking method
|
||||
* and then forwards the request to a wrapped MBeanServer object. The
|
||||
* checking method may throw a RuntimeException if the operation is
|
||||
* not allowed; in this case the request is not forwarded to the
|
||||
* wrapped object.</p>
|
||||
*
|
||||
* <p>A typical use of this class is to insert it between a connector server
|
||||
* such as the RMI connector and the MBeanServer with which the connector
|
||||
* is associated. Requests from the connector client can then be filtered
|
||||
* and those operations that are not allowed, or not allowed in a particular
|
||||
* context, can be rejected by throwing a <code>SecurityException</code>
|
||||
* in the corresponding <code>check*</code> method.</p>
|
||||
*
|
||||
* <p>This is an abstract class, because in its implementation none of
|
||||
* the checking methods does anything. To be useful, it must be
|
||||
* subclassed and at least one of the checking methods overridden to
|
||||
* do some checking. Some or all of the MBeanServer methods may also
|
||||
* be overridden, for instance if the default checking behavior is
|
||||
* inappropriate.</p>
|
||||
*
|
||||
* <p>If there is no SecurityManager, then the access controller will refuse
|
||||
* to create an MBean that is a ClassLoader, which includes MLets, or to
|
||||
* execute the method addURL on an MBean that is an MLet. This prevents
|
||||
* people from opening security holes unintentionally. Otherwise, it
|
||||
* would not be obvious that granting write access grants the ability to
|
||||
* download and execute arbitrary code in the target MBean server. Advanced
|
||||
* users who do want the ability to use MLets are presumably advanced enough
|
||||
* to handle policy files and security managers.</p>
|
||||
*/
|
||||
public abstract class MBeanServerAccessController
|
||||
implements MBeanServerForwarder {
|
||||
|
||||
public MBeanServer getMBeanServer() {
|
||||
return mbs;
|
||||
}
|
||||
|
||||
public void setMBeanServer(MBeanServer mbs) {
|
||||
if (mbs == null)
|
||||
throw new IllegalArgumentException("Null MBeanServer");
|
||||
if (this.mbs != null)
|
||||
throw new IllegalArgumentException("MBeanServer object already " +
|
||||
"initialized");
|
||||
this.mbs = mbs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the caller can do read operations. This method does
|
||||
* nothing if so, otherwise throws SecurityException.
|
||||
*/
|
||||
protected abstract void checkRead();
|
||||
|
||||
/**
|
||||
* Check if the caller can do write operations. This method does
|
||||
* nothing if so, otherwise throws SecurityException.
|
||||
*/
|
||||
protected abstract void checkWrite();
|
||||
|
||||
/**
|
||||
* Check if the caller can create the named class. The default
|
||||
* implementation of this method calls {@link #checkWrite()}.
|
||||
*/
|
||||
protected void checkCreate(String className) {
|
||||
checkWrite();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the caller can unregister the named MBean. The default
|
||||
* implementation of this method calls {@link #checkWrite()}.
|
||||
*/
|
||||
protected void checkUnregister(ObjectName name) {
|
||||
checkWrite();
|
||||
}
|
||||
|
||||
//--------------------------------------------
|
||||
//--------------------------------------------
|
||||
//
|
||||
// Implementation of the MBeanServer interface
|
||||
//
|
||||
//--------------------------------------------
|
||||
//--------------------------------------------
|
||||
|
||||
/**
|
||||
* Call <code>checkRead()</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public void addNotificationListener(ObjectName name,
|
||||
NotificationListener listener,
|
||||
NotificationFilter filter,
|
||||
Object handback)
|
||||
throws InstanceNotFoundException {
|
||||
checkRead();
|
||||
getMBeanServer().addNotificationListener(name, listener,
|
||||
filter, handback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkRead()</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public void addNotificationListener(ObjectName name,
|
||||
ObjectName listener,
|
||||
NotificationFilter filter,
|
||||
Object handback)
|
||||
throws InstanceNotFoundException {
|
||||
checkRead();
|
||||
getMBeanServer().addNotificationListener(name, listener,
|
||||
filter, handback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkCreate(className)</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public ObjectInstance createMBean(String className, ObjectName name)
|
||||
throws
|
||||
ReflectionException,
|
||||
InstanceAlreadyExistsException,
|
||||
MBeanRegistrationException,
|
||||
MBeanException,
|
||||
NotCompliantMBeanException {
|
||||
checkCreate(className);
|
||||
SecurityManager sm = System.getSecurityManager();
|
||||
if (sm == null) {
|
||||
Object object = getMBeanServer().instantiate(className);
|
||||
checkClassLoader(object);
|
||||
return getMBeanServer().registerMBean(object, name);
|
||||
} else {
|
||||
return getMBeanServer().createMBean(className, name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkCreate(className)</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public ObjectInstance createMBean(String className, ObjectName name,
|
||||
Object params[], String signature[])
|
||||
throws
|
||||
ReflectionException,
|
||||
InstanceAlreadyExistsException,
|
||||
MBeanRegistrationException,
|
||||
MBeanException,
|
||||
NotCompliantMBeanException {
|
||||
checkCreate(className);
|
||||
SecurityManager sm = System.getSecurityManager();
|
||||
if (sm == null) {
|
||||
Object object = getMBeanServer().instantiate(className,
|
||||
params,
|
||||
signature);
|
||||
checkClassLoader(object);
|
||||
return getMBeanServer().registerMBean(object, name);
|
||||
} else {
|
||||
return getMBeanServer().createMBean(className, name,
|
||||
params, signature);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkCreate(className)</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public ObjectInstance createMBean(String className,
|
||||
ObjectName name,
|
||||
ObjectName loaderName)
|
||||
throws
|
||||
ReflectionException,
|
||||
InstanceAlreadyExistsException,
|
||||
MBeanRegistrationException,
|
||||
MBeanException,
|
||||
NotCompliantMBeanException,
|
||||
InstanceNotFoundException {
|
||||
checkCreate(className);
|
||||
SecurityManager sm = System.getSecurityManager();
|
||||
if (sm == null) {
|
||||
Object object = getMBeanServer().instantiate(className,
|
||||
loaderName);
|
||||
checkClassLoader(object);
|
||||
return getMBeanServer().registerMBean(object, name);
|
||||
} else {
|
||||
return getMBeanServer().createMBean(className, name, loaderName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkCreate(className)</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public ObjectInstance createMBean(String className,
|
||||
ObjectName name,
|
||||
ObjectName loaderName,
|
||||
Object params[],
|
||||
String signature[])
|
||||
throws
|
||||
ReflectionException,
|
||||
InstanceAlreadyExistsException,
|
||||
MBeanRegistrationException,
|
||||
MBeanException,
|
||||
NotCompliantMBeanException,
|
||||
InstanceNotFoundException {
|
||||
checkCreate(className);
|
||||
SecurityManager sm = System.getSecurityManager();
|
||||
if (sm == null) {
|
||||
Object object = getMBeanServer().instantiate(className,
|
||||
loaderName,
|
||||
params,
|
||||
signature);
|
||||
checkClassLoader(object);
|
||||
return getMBeanServer().registerMBean(object, name);
|
||||
} else {
|
||||
return getMBeanServer().createMBean(className, name, loaderName,
|
||||
params, signature);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkRead()</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
@Deprecated
|
||||
public ObjectInputStream deserialize(ObjectName name, byte[] data)
|
||||
throws InstanceNotFoundException, OperationsException {
|
||||
checkRead();
|
||||
return getMBeanServer().deserialize(name, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkRead()</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
@Deprecated
|
||||
public ObjectInputStream deserialize(String className, byte[] data)
|
||||
throws OperationsException, ReflectionException {
|
||||
checkRead();
|
||||
return getMBeanServer().deserialize(className, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkRead()</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
@Deprecated
|
||||
public ObjectInputStream deserialize(String className,
|
||||
ObjectName loaderName,
|
||||
byte[] data)
|
||||
throws
|
||||
InstanceNotFoundException,
|
||||
OperationsException,
|
||||
ReflectionException {
|
||||
checkRead();
|
||||
return getMBeanServer().deserialize(className, loaderName, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkRead()</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public Object getAttribute(ObjectName name, String attribute)
|
||||
throws
|
||||
MBeanException,
|
||||
AttributeNotFoundException,
|
||||
InstanceNotFoundException,
|
||||
ReflectionException {
|
||||
checkRead();
|
||||
return getMBeanServer().getAttribute(name, attribute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkRead()</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public AttributeList getAttributes(ObjectName name, String[] attributes)
|
||||
throws InstanceNotFoundException, ReflectionException {
|
||||
checkRead();
|
||||
return getMBeanServer().getAttributes(name, attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkRead()</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public ClassLoader getClassLoader(ObjectName loaderName)
|
||||
throws InstanceNotFoundException {
|
||||
checkRead();
|
||||
return getMBeanServer().getClassLoader(loaderName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkRead()</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public ClassLoader getClassLoaderFor(ObjectName mbeanName)
|
||||
throws InstanceNotFoundException {
|
||||
checkRead();
|
||||
return getMBeanServer().getClassLoaderFor(mbeanName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkRead()</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public ClassLoaderRepository getClassLoaderRepository() {
|
||||
checkRead();
|
||||
return getMBeanServer().getClassLoaderRepository();
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkRead()</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public String getDefaultDomain() {
|
||||
checkRead();
|
||||
return getMBeanServer().getDefaultDomain();
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkRead()</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public String[] getDomains() {
|
||||
checkRead();
|
||||
return getMBeanServer().getDomains();
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkRead()</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public Integer getMBeanCount() {
|
||||
checkRead();
|
||||
return getMBeanServer().getMBeanCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkRead()</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public MBeanInfo getMBeanInfo(ObjectName name)
|
||||
throws
|
||||
InstanceNotFoundException,
|
||||
IntrospectionException,
|
||||
ReflectionException {
|
||||
checkRead();
|
||||
return getMBeanServer().getMBeanInfo(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkRead()</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public ObjectInstance getObjectInstance(ObjectName name)
|
||||
throws InstanceNotFoundException {
|
||||
checkRead();
|
||||
return getMBeanServer().getObjectInstance(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkCreate(className)</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public Object instantiate(String className)
|
||||
throws ReflectionException, MBeanException {
|
||||
checkCreate(className);
|
||||
return getMBeanServer().instantiate(className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkCreate(className)</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public Object instantiate(String className,
|
||||
Object params[],
|
||||
String signature[])
|
||||
throws ReflectionException, MBeanException {
|
||||
checkCreate(className);
|
||||
return getMBeanServer().instantiate(className, params, signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkCreate(className)</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public Object instantiate(String className, ObjectName loaderName)
|
||||
throws ReflectionException, MBeanException, InstanceNotFoundException {
|
||||
checkCreate(className);
|
||||
return getMBeanServer().instantiate(className, loaderName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkCreate(className)</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public Object instantiate(String className, ObjectName loaderName,
|
||||
Object params[], String signature[])
|
||||
throws ReflectionException, MBeanException, InstanceNotFoundException {
|
||||
checkCreate(className);
|
||||
return getMBeanServer().instantiate(className, loaderName,
|
||||
params, signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkWrite()</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public Object invoke(ObjectName name, String operationName,
|
||||
Object params[], String signature[])
|
||||
throws
|
||||
InstanceNotFoundException,
|
||||
MBeanException,
|
||||
ReflectionException {
|
||||
checkWrite();
|
||||
checkMLetMethods(name, operationName);
|
||||
return getMBeanServer().invoke(name, operationName, params, signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkRead()</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public boolean isInstanceOf(ObjectName name, String className)
|
||||
throws InstanceNotFoundException {
|
||||
checkRead();
|
||||
return getMBeanServer().isInstanceOf(name, className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkRead()</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public boolean isRegistered(ObjectName name) {
|
||||
checkRead();
|
||||
return getMBeanServer().isRegistered(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkRead()</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public Set<ObjectInstance> queryMBeans(ObjectName name, QueryExp query) {
|
||||
checkRead();
|
||||
return getMBeanServer().queryMBeans(name, query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkRead()</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public Set<ObjectName> queryNames(ObjectName name, QueryExp query) {
|
||||
checkRead();
|
||||
return getMBeanServer().queryNames(name, query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkWrite()</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public ObjectInstance registerMBean(Object object, ObjectName name)
|
||||
throws
|
||||
InstanceAlreadyExistsException,
|
||||
MBeanRegistrationException,
|
||||
NotCompliantMBeanException {
|
||||
checkWrite();
|
||||
return getMBeanServer().registerMBean(object, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkRead()</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public void removeNotificationListener(ObjectName name,
|
||||
NotificationListener listener)
|
||||
throws InstanceNotFoundException, ListenerNotFoundException {
|
||||
checkRead();
|
||||
getMBeanServer().removeNotificationListener(name, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkRead()</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public void removeNotificationListener(ObjectName name,
|
||||
NotificationListener listener,
|
||||
NotificationFilter filter,
|
||||
Object handback)
|
||||
throws InstanceNotFoundException, ListenerNotFoundException {
|
||||
checkRead();
|
||||
getMBeanServer().removeNotificationListener(name, listener,
|
||||
filter, handback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkRead()</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public void removeNotificationListener(ObjectName name,
|
||||
ObjectName listener)
|
||||
throws InstanceNotFoundException, ListenerNotFoundException {
|
||||
checkRead();
|
||||
getMBeanServer().removeNotificationListener(name, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkRead()</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public void removeNotificationListener(ObjectName name,
|
||||
ObjectName listener,
|
||||
NotificationFilter filter,
|
||||
Object handback)
|
||||
throws InstanceNotFoundException, ListenerNotFoundException {
|
||||
checkRead();
|
||||
getMBeanServer().removeNotificationListener(name, listener,
|
||||
filter, handback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkWrite()</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public void setAttribute(ObjectName name, Attribute attribute)
|
||||
throws
|
||||
InstanceNotFoundException,
|
||||
AttributeNotFoundException,
|
||||
InvalidAttributeValueException,
|
||||
MBeanException,
|
||||
ReflectionException {
|
||||
checkWrite();
|
||||
getMBeanServer().setAttribute(name, attribute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkWrite()</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public AttributeList setAttributes(ObjectName name,
|
||||
AttributeList attributes)
|
||||
throws InstanceNotFoundException, ReflectionException {
|
||||
checkWrite();
|
||||
return getMBeanServer().setAttributes(name, attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call <code>checkUnregister()</code>, then forward this method to the
|
||||
* wrapped object.
|
||||
*/
|
||||
public void unregisterMBean(ObjectName name)
|
||||
throws InstanceNotFoundException, MBeanRegistrationException {
|
||||
checkUnregister(name);
|
||||
getMBeanServer().unregisterMBean(name);
|
||||
}
|
||||
|
||||
//----------------
|
||||
// PRIVATE METHODS
|
||||
//----------------
|
||||
|
||||
private void checkClassLoader(Object object) {
|
||||
if (object instanceof ClassLoader)
|
||||
throw new SecurityException("Access denied! Creating an " +
|
||||
"MBean that is a ClassLoader " +
|
||||
"is forbidden unless a security " +
|
||||
"manager is installed.");
|
||||
}
|
||||
|
||||
private void checkMLetMethods(ObjectName name, String operation)
|
||||
throws InstanceNotFoundException {
|
||||
// Check if security manager installed
|
||||
SecurityManager sm = System.getSecurityManager();
|
||||
if (sm != null) {
|
||||
return;
|
||||
}
|
||||
// Check for addURL and getMBeansFromURL methods
|
||||
if (!operation.equals("addURL") &&
|
||||
!operation.equals("getMBeansFromURL")) {
|
||||
return;
|
||||
}
|
||||
// Check if MBean is instance of MLet
|
||||
if (!getMBeanServer().isInstanceOf(name,
|
||||
"javax.management.loading.MLet")) {
|
||||
return;
|
||||
}
|
||||
// Throw security exception
|
||||
if (operation.equals("addURL")) { // addURL
|
||||
throw new SecurityException("Access denied! MLet method addURL " +
|
||||
"cannot be invoked unless a security manager is installed.");
|
||||
} else { // getMBeansFromURL
|
||||
// Whether or not calling getMBeansFromURL is allowed is controlled
|
||||
// by the value of the "jmx.remote.x.mlet.allow.getMBeansFromURL"
|
||||
// system property. If the value of this property is true, calling
|
||||
// the MLet's getMBeansFromURL method is allowed. The default value
|
||||
// for this property is false.
|
||||
final String propName = "jmx.remote.x.mlet.allow.getMBeansFromURL";
|
||||
GetPropertyAction propAction = new GetPropertyAction(propName);
|
||||
String propValue = AccessController.doPrivileged(propAction);
|
||||
boolean allowGetMBeansFromURL = "true".equalsIgnoreCase(propValue);
|
||||
if (!allowGetMBeansFromURL) {
|
||||
throw new SecurityException("Access denied! MLet method " +
|
||||
"getMBeansFromURL cannot be invoked unless a " +
|
||||
"security manager is installed or the system property " +
|
||||
"-Djmx.remote.x.mlet.allow.getMBeansFromURL=true " +
|
||||
"is specified.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//------------------
|
||||
// PRIVATE VARIABLES
|
||||
//------------------
|
||||
|
||||
private MBeanServer mbs;
|
||||
}
|
||||
@@ -0,0 +1,544 @@
|
||||
/*
|
||||
* Copyright (c) 2003, 2008, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code 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
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package com.sun.jmx.remote.security;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.AccessControlContext;
|
||||
import java.security.AccessController;
|
||||
import java.security.Principal;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.management.MBeanServer;
|
||||
import javax.management.ObjectName;
|
||||
import javax.security.auth.Subject;
|
||||
|
||||
/**
|
||||
* <p>An object of this class implements the MBeanServerAccessController
|
||||
* interface and, for each of its methods, calls an appropriate checking
|
||||
* method and then forwards the request to a wrapped MBeanServer object.
|
||||
* The checking method may throw a SecurityException if the operation is
|
||||
* not allowed; in this case the request is not forwarded to the
|
||||
* wrapped object.</p>
|
||||
*
|
||||
* <p>This class implements the {@link #checkRead()}, {@link #checkWrite()},
|
||||
* {@link #checkCreate(String)}, and {@link #checkUnregister(ObjectName)}
|
||||
* methods based on an access level properties file containing username/access
|
||||
* level pairs. The set of username/access level pairs is passed either as a
|
||||
* filename which denotes a properties file on disk, or directly as an instance
|
||||
* of the {@link Properties} class. In both cases, the name of each property
|
||||
* represents a username, and the value of the property is the associated access
|
||||
* level. Thus, any given username either does not exist in the properties or
|
||||
* has exactly one access level. The same access level can be shared by several
|
||||
* usernames.</p>
|
||||
*
|
||||
* <p>The supported access level values are {@code readonly} and
|
||||
* {@code readwrite}. The {@code readwrite} access level can be
|
||||
* qualified by one or more <i>clauses</i>, where each clause looks
|
||||
* like <code>create <i>classNamePattern</i></code> or {@code
|
||||
* unregister}. For example:</p>
|
||||
*
|
||||
* <pre>
|
||||
* monitorRole readonly
|
||||
* controlRole readwrite \
|
||||
* create javax.management.timer.*,javax.management.monitor.* \
|
||||
* unregister
|
||||
* </pre>
|
||||
*
|
||||
* <p>(The continuation lines with {@code \} come from the parser for
|
||||
* Properties files.)</p>
|
||||
*/
|
||||
public class MBeanServerFileAccessController
|
||||
extends MBeanServerAccessController {
|
||||
|
||||
static final String READONLY = "readonly";
|
||||
static final String READWRITE = "readwrite";
|
||||
|
||||
static final String CREATE = "create";
|
||||
static final String UNREGISTER = "unregister";
|
||||
|
||||
private enum AccessType {READ, WRITE, CREATE, UNREGISTER};
|
||||
|
||||
private static class Access {
|
||||
final boolean write;
|
||||
final String[] createPatterns;
|
||||
private boolean unregister;
|
||||
|
||||
Access(boolean write, boolean unregister, List<String> createPatternList) {
|
||||
this.write = write;
|
||||
int npats = (createPatternList == null) ? 0 : createPatternList.size();
|
||||
if (npats == 0)
|
||||
this.createPatterns = NO_STRINGS;
|
||||
else
|
||||
this.createPatterns = createPatternList.toArray(new String[npats]);
|
||||
this.unregister = unregister;
|
||||
}
|
||||
|
||||
private final String[] NO_STRINGS = new String[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Create a new MBeanServerAccessController that forwards all the
|
||||
* MBeanServer requests to the MBeanServer set by invoking the {@link
|
||||
* #setMBeanServer} method after doing access checks based on read and
|
||||
* write permissions.</p>
|
||||
*
|
||||
* <p>This instance is initialized from the specified properties file.</p>
|
||||
*
|
||||
* @param accessFileName name of the file which denotes a properties
|
||||
* file on disk containing the username/access level entries.
|
||||
*
|
||||
* @exception IOException if the file does not exist, is a
|
||||
* directory rather than a regular file, or for some other
|
||||
* reason cannot be opened for reading.
|
||||
*
|
||||
* @exception IllegalArgumentException if any of the supplied access
|
||||
* level values differs from "readonly" or "readwrite".
|
||||
*/
|
||||
public MBeanServerFileAccessController(String accessFileName)
|
||||
throws IOException {
|
||||
super();
|
||||
this.accessFileName = accessFileName;
|
||||
Properties props = propertiesFromFile(accessFileName);
|
||||
parseProperties(props);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Create a new MBeanServerAccessController that forwards all the
|
||||
* MBeanServer requests to <code>mbs</code> after doing access checks
|
||||
* based on read and write permissions.</p>
|
||||
*
|
||||
* <p>This instance is initialized from the specified properties file.</p>
|
||||
*
|
||||
* @param accessFileName name of the file which denotes a properties
|
||||
* file on disk containing the username/access level entries.
|
||||
*
|
||||
* @param mbs the MBeanServer object to which requests will be forwarded.
|
||||
*
|
||||
* @exception IOException if the file does not exist, is a
|
||||
* directory rather than a regular file, or for some other
|
||||
* reason cannot be opened for reading.
|
||||
*
|
||||
* @exception IllegalArgumentException if any of the supplied access
|
||||
* level values differs from "readonly" or "readwrite".
|
||||
*/
|
||||
public MBeanServerFileAccessController(String accessFileName,
|
||||
MBeanServer mbs)
|
||||
throws IOException {
|
||||
this(accessFileName);
|
||||
setMBeanServer(mbs);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Create a new MBeanServerAccessController that forwards all the
|
||||
* MBeanServer requests to the MBeanServer set by invoking the {@link
|
||||
* #setMBeanServer} method after doing access checks based on read and
|
||||
* write permissions.</p>
|
||||
*
|
||||
* <p>This instance is initialized from the specified properties
|
||||
* instance. This constructor makes a copy of the properties
|
||||
* instance and it is the copy that is consulted to check the
|
||||
* username and access level of an incoming connection. The
|
||||
* original properties object can be modified without affecting
|
||||
* the copy. If the {@link #refresh} method is then called, the
|
||||
* <code>MBeanServerFileAccessController</code> will make a new
|
||||
* copy of the properties object at that time.</p>
|
||||
*
|
||||
* @param accessFileProps properties list containing the username/access
|
||||
* level entries.
|
||||
*
|
||||
* @exception IllegalArgumentException if <code>accessFileProps</code> is
|
||||
* <code>null</code> or if any of the supplied access level values differs
|
||||
* from "readonly" or "readwrite".
|
||||
*/
|
||||
public MBeanServerFileAccessController(Properties accessFileProps)
|
||||
throws IOException {
|
||||
super();
|
||||
if (accessFileProps == null)
|
||||
throw new IllegalArgumentException("Null properties");
|
||||
originalProps = accessFileProps;
|
||||
parseProperties(accessFileProps);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Create a new MBeanServerAccessController that forwards all the
|
||||
* MBeanServer requests to the MBeanServer set by invoking the {@link
|
||||
* #setMBeanServer} method after doing access checks based on read and
|
||||
* write permissions.</p>
|
||||
*
|
||||
* <p>This instance is initialized from the specified properties
|
||||
* instance. This constructor makes a copy of the properties
|
||||
* instance and it is the copy that is consulted to check the
|
||||
* username and access level of an incoming connection. The
|
||||
* original properties object can be modified without affecting
|
||||
* the copy. If the {@link #refresh} method is then called, the
|
||||
* <code>MBeanServerFileAccessController</code> will make a new
|
||||
* copy of the properties object at that time.</p>
|
||||
*
|
||||
* @param accessFileProps properties list containing the username/access
|
||||
* level entries.
|
||||
*
|
||||
* @param mbs the MBeanServer object to which requests will be forwarded.
|
||||
*
|
||||
* @exception IllegalArgumentException if <code>accessFileProps</code> is
|
||||
* <code>null</code> or if any of the supplied access level values differs
|
||||
* from "readonly" or "readwrite".
|
||||
*/
|
||||
public MBeanServerFileAccessController(Properties accessFileProps,
|
||||
MBeanServer mbs)
|
||||
throws IOException {
|
||||
this(accessFileProps);
|
||||
setMBeanServer(mbs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the caller can do read operations. This method does
|
||||
* nothing if so, otherwise throws SecurityException.
|
||||
*/
|
||||
@Override
|
||||
public void checkRead() {
|
||||
checkAccess(AccessType.READ, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the caller can do write operations. This method does
|
||||
* nothing if so, otherwise throws SecurityException.
|
||||
*/
|
||||
@Override
|
||||
public void checkWrite() {
|
||||
checkAccess(AccessType.WRITE, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the caller can create MBeans or instances of the given class.
|
||||
* This method does nothing if so, otherwise throws SecurityException.
|
||||
*/
|
||||
@Override
|
||||
public void checkCreate(String className) {
|
||||
checkAccess(AccessType.CREATE, className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the caller can do unregister operations. This method does
|
||||
* nothing if so, otherwise throws SecurityException.
|
||||
*/
|
||||
@Override
|
||||
public void checkUnregister(ObjectName name) {
|
||||
checkAccess(AccessType.UNREGISTER, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Refresh the set of username/access level entries.</p>
|
||||
*
|
||||
* <p>If this instance was created using the
|
||||
* {@link #MBeanServerFileAccessController(String)} or
|
||||
* {@link #MBeanServerFileAccessController(String,MBeanServer)}
|
||||
* constructors to specify a file from which the entries are read,
|
||||
* the file is re-read.</p>
|
||||
*
|
||||
* <p>If this instance was created using the
|
||||
* {@link #MBeanServerFileAccessController(Properties)} or
|
||||
* {@link #MBeanServerFileAccessController(Properties,MBeanServer)}
|
||||
* constructors then a new copy of the <code>Properties</code> object
|
||||
* is made.</p>
|
||||
*
|
||||
* @exception IOException if the file does not exist, is a
|
||||
* directory rather than a regular file, or for some other
|
||||
* reason cannot be opened for reading.
|
||||
*
|
||||
* @exception IllegalArgumentException if any of the supplied access
|
||||
* level values differs from "readonly" or "readwrite".
|
||||
*/
|
||||
public synchronized void refresh() throws IOException {
|
||||
Properties props;
|
||||
if (accessFileName == null)
|
||||
props = (Properties) originalProps;
|
||||
else
|
||||
props = propertiesFromFile(accessFileName);
|
||||
parseProperties(props);
|
||||
}
|
||||
|
||||
private static Properties propertiesFromFile(String fname)
|
||||
throws IOException {
|
||||
FileInputStream fin = new FileInputStream(fname);
|
||||
try {
|
||||
Properties p = new Properties();
|
||||
p.load(fin);
|
||||
return p;
|
||||
} finally {
|
||||
fin.close();
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void checkAccess(AccessType requiredAccess, String arg) {
|
||||
final AccessControlContext acc = AccessController.getContext();
|
||||
final Subject s =
|
||||
AccessController.doPrivileged(new PrivilegedAction<Subject>() {
|
||||
public Subject run() {
|
||||
return Subject.getSubject(acc);
|
||||
}
|
||||
});
|
||||
if (s == null) return; /* security has not been enabled */
|
||||
final Set principals = s.getPrincipals();
|
||||
String newPropertyValue = null;
|
||||
for (Iterator i = principals.iterator(); i.hasNext(); ) {
|
||||
final Principal p = (Principal) i.next();
|
||||
Access access = accessMap.get(p.getName());
|
||||
if (access != null) {
|
||||
boolean ok;
|
||||
switch (requiredAccess) {
|
||||
case READ:
|
||||
ok = true; // all access entries imply read
|
||||
break;
|
||||
case WRITE:
|
||||
ok = access.write;
|
||||
break;
|
||||
case UNREGISTER:
|
||||
ok = access.unregister;
|
||||
if (!ok && access.write)
|
||||
newPropertyValue = "unregister";
|
||||
break;
|
||||
case CREATE:
|
||||
ok = checkCreateAccess(access, arg);
|
||||
if (!ok && access.write)
|
||||
newPropertyValue = "create " + arg;
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
if (ok)
|
||||
return;
|
||||
}
|
||||
}
|
||||
SecurityException se = new SecurityException("Access denied! Invalid " +
|
||||
"access level for requested MBeanServer operation.");
|
||||
// Add some more information to help people with deployments that
|
||||
// worked before we required explicit create clauses. We're not giving
|
||||
// any information to the bad guys, other than that the access control
|
||||
// is based on a file, which they could have worked out from the stack
|
||||
// trace anyway.
|
||||
if (newPropertyValue != null) {
|
||||
SecurityException se2 = new SecurityException("Access property " +
|
||||
"for this identity should be similar to: " + READWRITE +
|
||||
" " + newPropertyValue);
|
||||
se.initCause(se2);
|
||||
}
|
||||
throw se;
|
||||
}
|
||||
|
||||
private static boolean checkCreateAccess(Access access, String className) {
|
||||
for (String classNamePattern : access.createPatterns) {
|
||||
if (classNameMatch(classNamePattern, className))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean classNameMatch(String pattern, String className) {
|
||||
// We studiously avoided regexes when parsing the properties file,
|
||||
// because that is done whenever the VM is started with the
|
||||
// appropriate -Dcom.sun.management options, even if nobody ever
|
||||
// creates an MBean. We don't want to incur the overhead of loading
|
||||
// all the regex code whenever those options are specified, but if we
|
||||
// get as far as here then the VM is already running and somebody is
|
||||
// doing the very unusual operation of remotely creating an MBean.
|
||||
// Because that operation is so unusual, we don't try to optimize
|
||||
// by hand-matching or by caching compiled Pattern objects.
|
||||
StringBuilder sb = new StringBuilder();
|
||||
StringTokenizer stok = new StringTokenizer(pattern, "*", true);
|
||||
while (stok.hasMoreTokens()) {
|
||||
String tok = stok.nextToken();
|
||||
if (tok.equals("*"))
|
||||
sb.append("[^.]*");
|
||||
else
|
||||
sb.append(Pattern.quote(tok));
|
||||
}
|
||||
return className.matches(sb.toString());
|
||||
}
|
||||
|
||||
private void parseProperties(Properties props) {
|
||||
this.accessMap = new HashMap<String, Access>();
|
||||
for (Map.Entry<Object, Object> entry : props.entrySet()) {
|
||||
String identity = (String) entry.getKey();
|
||||
String accessString = (String) entry.getValue();
|
||||
Access access = Parser.parseAccess(identity, accessString);
|
||||
accessMap.put(identity, access);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Parser {
|
||||
private final static int EOS = -1; // pseudo-codepoint "end of string"
|
||||
static {
|
||||
assert !Character.isWhitespace(EOS);
|
||||
}
|
||||
|
||||
private final String identity; // just for better error messages
|
||||
private final String s; // the string we're parsing
|
||||
private final int len; // s.length()
|
||||
private int i;
|
||||
private int c;
|
||||
// At any point, either c is s.codePointAt(i), or i == len and
|
||||
// c is EOS. We use int rather than char because it is conceivable
|
||||
// (if unlikely) that a classname in a create clause might contain
|
||||
// "supplementary characters", the ones that don't fit in the original
|
||||
// 16 bits for Unicode.
|
||||
|
||||
private Parser(String identity, String s) {
|
||||
this.identity = identity;
|
||||
this.s = s;
|
||||
this.len = s.length();
|
||||
this.i = 0;
|
||||
if (i < len)
|
||||
this.c = s.codePointAt(i);
|
||||
else
|
||||
this.c = EOS;
|
||||
}
|
||||
|
||||
static Access parseAccess(String identity, String s) {
|
||||
return new Parser(identity, s).parseAccess();
|
||||
}
|
||||
|
||||
private Access parseAccess() {
|
||||
skipSpace();
|
||||
String type = parseWord();
|
||||
Access access;
|
||||
if (type.equals(READONLY))
|
||||
access = new Access(false, false, null);
|
||||
else if (type.equals(READWRITE))
|
||||
access = parseReadWrite();
|
||||
else {
|
||||
throw syntax("Expected " + READONLY + " or " + READWRITE +
|
||||
": " + type);
|
||||
}
|
||||
if (c != EOS)
|
||||
throw syntax("Extra text at end of line");
|
||||
return access;
|
||||
}
|
||||
|
||||
private Access parseReadWrite() {
|
||||
List<String> createClasses = new ArrayList<String>();
|
||||
boolean unregister = false;
|
||||
while (true) {
|
||||
skipSpace();
|
||||
if (c == EOS)
|
||||
break;
|
||||
String type = parseWord();
|
||||
if (type.equals(UNREGISTER))
|
||||
unregister = true;
|
||||
else if (type.equals(CREATE))
|
||||
parseCreate(createClasses);
|
||||
else
|
||||
throw syntax("Unrecognized keyword " + type);
|
||||
}
|
||||
return new Access(true, unregister, createClasses);
|
||||
}
|
||||
|
||||
private void parseCreate(List<String> createClasses) {
|
||||
while (true) {
|
||||
skipSpace();
|
||||
createClasses.add(parseClassName());
|
||||
skipSpace();
|
||||
if (c == ',')
|
||||
next();
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private String parseClassName() {
|
||||
// We don't check that classname components begin with suitable
|
||||
// characters (so we accept 1.2.3 for example). This means that
|
||||
// there are only two states, which we can call dotOK and !dotOK
|
||||
// according as a dot (.) is legal or not. Initially we're in
|
||||
// !dotOK since a classname can't start with a dot; after a dot
|
||||
// we're in !dotOK again; and after any other characters we're in
|
||||
// dotOK. The classname is only accepted if we end in dotOK,
|
||||
// so we reject an empty name or a name that ends with a dot.
|
||||
final int start = i;
|
||||
boolean dotOK = false;
|
||||
while (true) {
|
||||
if (c == '.') {
|
||||
if (!dotOK)
|
||||
throw syntax("Bad . in class name");
|
||||
dotOK = false;
|
||||
} else if (c == '*' || Character.isJavaIdentifierPart(c))
|
||||
dotOK = true;
|
||||
else
|
||||
break;
|
||||
next();
|
||||
}
|
||||
String className = s.substring(start, i);
|
||||
if (!dotOK)
|
||||
throw syntax("Bad class name " + className);
|
||||
return className;
|
||||
}
|
||||
|
||||
// Advance c and i to the next character, unless already at EOS.
|
||||
private void next() {
|
||||
if (c != EOS) {
|
||||
i += Character.charCount(c);
|
||||
if (i < len)
|
||||
c = s.codePointAt(i);
|
||||
else
|
||||
c = EOS;
|
||||
}
|
||||
}
|
||||
|
||||
private void skipSpace() {
|
||||
while (Character.isWhitespace(c))
|
||||
next();
|
||||
}
|
||||
|
||||
private String parseWord() {
|
||||
skipSpace();
|
||||
if (c == EOS)
|
||||
throw syntax("Expected word at end of line");
|
||||
final int start = i;
|
||||
while (c != EOS && !Character.isWhitespace(c))
|
||||
next();
|
||||
String word = s.substring(start, i);
|
||||
skipSpace();
|
||||
return word;
|
||||
}
|
||||
|
||||
private IllegalArgumentException syntax(String msg) {
|
||||
return new IllegalArgumentException(
|
||||
msg + " [" + identity + " " + s + "]");
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Access> accessMap;
|
||||
private Properties originalProps;
|
||||
private String accessFileName;
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code 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
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package com.sun.jmx.remote.security;
|
||||
|
||||
import javax.management.Notification;
|
||||
import javax.management.ObjectName;
|
||||
import javax.security.auth.Subject;
|
||||
|
||||
/**
|
||||
* <p>This interface allows to control remote access to the
|
||||
* {@code addNotificationListener} and {@code removeNotificationListener}
|
||||
* methods when the notification listener parameter is of type
|
||||
* {@code NotificationListener} and also allows to control remote access
|
||||
* to the notifications being forwarded to the interested remote listeners.</p>
|
||||
*
|
||||
* <p>An implementation of this interface can be supplied to a
|
||||
* {@code JMXConnectorServer} in the environment map through the
|
||||
* {@code com.sun.jmx.remote.notification.access.controller}
|
||||
* environment map property.</p>
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public interface NotificationAccessController {
|
||||
|
||||
/**
|
||||
* This method is called when a remote
|
||||
* {@link javax.management.remote.JMXConnector} invokes the method
|
||||
* {@link javax.management.MBeanServerConnection#addNotificationListener(ObjectName,NotificationListener,NotificationFilter,Object)}.
|
||||
*
|
||||
* @param connectionId the {@code connectionId} of the remote client
|
||||
* adding the listener.
|
||||
* @param name the name of the MBean where the listener is to be added.
|
||||
* @param subject the authenticated subject representing the remote client.
|
||||
*
|
||||
* @throws SecurityException if the remote client with the supplied
|
||||
* authenticated subject does not have the rights to add a listener
|
||||
* to the supplied MBean.
|
||||
*/
|
||||
public void addNotificationListener(String connectionId,
|
||||
ObjectName name,
|
||||
Subject subject)
|
||||
throws SecurityException;
|
||||
|
||||
/**
|
||||
* This method is called when a remote
|
||||
* {@link javax.management.remote.JMXConnector} invokes the method
|
||||
* {@link javax.management.MBeanServerConnection#removeNotificationListener(ObjectName,NotificationListener)}
|
||||
* or the method
|
||||
* {@link javax.management.MBeanServerConnection#removeNotificationListener(ObjectName,NotificationListener,NotificationFilter,Object)}.
|
||||
*
|
||||
* @param connectionId the {@code connectionId} of the remote client
|
||||
* removing the listener.
|
||||
* @param name the name of the MBean where the listener is to be removed.
|
||||
* @param subject the authenticated subject representing the remote client.
|
||||
*
|
||||
* @throws SecurityException if the remote client with the supplied
|
||||
* authenticated subject does not have the rights to remove a listener
|
||||
* from the supplied MBean.
|
||||
*/
|
||||
public void removeNotificationListener(String connectionId,
|
||||
ObjectName name,
|
||||
Subject subject)
|
||||
throws SecurityException;
|
||||
|
||||
/**
|
||||
* This method is called before the
|
||||
* {@link javax.management.remote.JMXConnectorServer}
|
||||
* forwards the notification to the interested remote
|
||||
* listener represented by the authenticated subject.
|
||||
*
|
||||
* @param connectionId the {@code connectionId} of the remote client
|
||||
* receiving the notification.
|
||||
* @param name the name of the MBean forwarding the notification.
|
||||
* @param notification the notification to be forwarded to the interested
|
||||
* remote listener.
|
||||
* @param subject the authenticated subject representing the remote client.
|
||||
*
|
||||
* @throws SecurityException if the remote client with
|
||||
* the supplied authenticated subject does not have the
|
||||
* rights to receive the notification.
|
||||
*/
|
||||
public void fetchNotification(String connectionId,
|
||||
ObjectName name,
|
||||
Notification notification,
|
||||
Subject subject)
|
||||
throws SecurityException;
|
||||
}
|
||||
125
jdkSrc/jdk8/com/sun/jmx/remote/security/SubjectDelegator.java
Normal file
125
jdkSrc/jdk8/com/sun/jmx/remote/security/SubjectDelegator.java
Normal file
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright (c) 2003, 2014, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code 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
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package com.sun.jmx.remote.security;
|
||||
|
||||
import java.security.AccessController;
|
||||
import java.security.AccessControlContext;
|
||||
import java.security.Permission;
|
||||
import java.security.Principal;
|
||||
import java.security.PrivilegedAction;
|
||||
import javax.security.auth.Subject;
|
||||
|
||||
import javax.management.remote.SubjectDelegationPermission;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class SubjectDelegator {
|
||||
/* Return the AccessControlContext appropriate to execute an
|
||||
operation on behalf of the delegatedSubject. If the
|
||||
authenticatedAccessControlContext does not have permission to
|
||||
delegate to that subject, throw SecurityException. */
|
||||
public AccessControlContext
|
||||
delegatedContext(AccessControlContext authenticatedACC,
|
||||
Subject delegatedSubject,
|
||||
boolean removeCallerContext)
|
||||
throws SecurityException {
|
||||
|
||||
if (System.getSecurityManager() != null && authenticatedACC == null) {
|
||||
throw new SecurityException("Illegal AccessControlContext: null");
|
||||
}
|
||||
|
||||
// Check if the subject delegation permission allows the
|
||||
// authenticated subject to assume the identity of each
|
||||
// principal in the delegated subject
|
||||
//
|
||||
Collection<Principal> ps = getSubjectPrincipals(delegatedSubject);
|
||||
final Collection<Permission> permissions = new ArrayList<>(ps.size());
|
||||
for(Principal p : ps) {
|
||||
final String pname = p.getClass().getName() + "." + p.getName();
|
||||
permissions.add(new SubjectDelegationPermission(pname));
|
||||
}
|
||||
PrivilegedAction<Void> action =
|
||||
new PrivilegedAction<Void>() {
|
||||
public Void run() {
|
||||
for (Permission sdp : permissions) {
|
||||
AccessController.checkPermission(sdp);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
AccessController.doPrivileged(action, authenticatedACC);
|
||||
|
||||
return getDelegatedAcc(delegatedSubject, removeCallerContext);
|
||||
}
|
||||
|
||||
private AccessControlContext getDelegatedAcc(Subject delegatedSubject, boolean removeCallerContext) {
|
||||
if (removeCallerContext) {
|
||||
return JMXSubjectDomainCombiner.getDomainCombinerContext(delegatedSubject);
|
||||
} else {
|
||||
return JMXSubjectDomainCombiner.getContext(delegatedSubject);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the connector server creator can assume the identity of each
|
||||
* principal in the authenticated subject, i.e. check if the connector
|
||||
* server creator codebase contains a subject delegation permission for
|
||||
* each principal present in the authenticated subject.
|
||||
*
|
||||
* @return {@code true} if the connector server creator can delegate to all
|
||||
* the authenticated principals in the subject. Otherwise, {@code false}.
|
||||
*/
|
||||
public static synchronized boolean
|
||||
checkRemoveCallerContext(Subject subject) {
|
||||
try {
|
||||
for (Principal p : getSubjectPrincipals(subject)) {
|
||||
final String pname =
|
||||
p.getClass().getName() + "." + p.getName();
|
||||
final Permission sdp =
|
||||
new SubjectDelegationPermission(pname);
|
||||
AccessController.checkPermission(sdp);
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the {@linkplain Subject} principals
|
||||
* @param subject The subject
|
||||
* @return If the {@code Subject} is immutable it will return the principals directly.
|
||||
* If the {@code Subject} is mutable it will create an unmodifiable copy.
|
||||
*/
|
||||
private static Collection<Principal> getSubjectPrincipals(Subject subject) {
|
||||
if (subject.isReadOnly()) {
|
||||
return subject.getPrincipals();
|
||||
}
|
||||
|
||||
List<Principal> principals = Arrays.asList(subject.getPrincipals().toArray(new Principal[0]));
|
||||
return Collections.unmodifiableList(principals);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user