/**
*
* Copyright
* 2009-2015 Jayway Products AB
* 2016-2017 Föreningen Sambruk
*
* Licensed under AGPL, Version 3.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.gnu.org/licenses/agpl.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package se.streamsource.streamflow.web.management.jmxconnector;
import org.qi4j.api.configuration.Configuration;
import org.qi4j.api.injection.scope.Service;
import org.qi4j.api.injection.scope.Structure;
import org.qi4j.api.injection.scope.This;
import org.qi4j.api.mixin.Mixins;
import org.qi4j.api.service.Activatable;
import org.qi4j.api.service.ServiceComposite;
import org.qi4j.api.structure.Module;
import org.qi4j.api.unitofwork.UnitOfWork;
import org.qi4j.api.usecase.UsecaseBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import se.streamsource.streamflow.web.domain.interaction.security.Authentication;
import se.streamsource.streamflow.web.domain.structure.user.UserAuthentication;
import javax.management.MBeanServer;
import javax.management.remote.JMXAuthenticator;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXPrincipal;
import javax.management.remote.JMXServiceURL;
import javax.security.auth.Subject;
import java.net.InetAddress;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* This service starts a JMX RMI connector. It also creates an RMI-registry
* to register the connector. The service is configured by changing the
* settings in the JmxConnectorConfiguration.
* <p/>
* Authentication is done using the "administrator" account in Streamflow.
*/
@Mixins(JmxConnectorService.JmxConnectorMixin.class)
public interface JmxConnectorService
extends Configuration, ServiceComposite, Activatable
{
class JmxConnectorMixin
implements Activatable
{
final Logger logger = LoggerFactory.getLogger(JmxConnectorService.class.getName());
@This
Configuration<JmxConnectorConfiguration> config;
@Service
MBeanServer server;
@Structure
Module module;
Registry registry;
JMXConnectorServer connector;
public void activate() throws Exception
{
if (config.configuration().enabled().get())
{
// see java.rmi.server.ObjID
System.setProperty("java.rmi.server.randomIDs", "true");
int jmxAgentPort = config.configuration().port().get();
try
{
registry = LocateRegistry.createRegistry(jmxAgentPort);
} catch (RemoteException e)
{
registry = LocateRegistry.getRegistry(jmxAgentPort);
}
String hostName = InetAddress.getLocalHost().getHostName();
JMXServiceURL url = new JMXServiceURL(
"service:jmx:rmi://" + hostName + ":" + jmxAgentPort
+ "/jndi/rmi://" + hostName + ":" + jmxAgentPort + "/jmxrmi");
Map env = new HashMap();
env.put(JMXConnectorServer.AUTHENTICATOR, new StreamflowJmxAuthenticator());
try
{
connector = JMXConnectorServerFactory.newJMXConnectorServer(url, env, server);
connector.start();
} catch (Exception e)
{
logger.error("Could not start JMX connector", e);
}
}
}
public void passivate() throws Exception
{
// Stop connector
if (connector != null)
{
connector.stop();
connector = null;
}
// Remove registry
if (registry != null)
{
UnicastRemoteObject.unexportObject(registry, true);
registry = null;
}
}
class StreamflowJmxAuthenticator implements JMXAuthenticator
{
public Subject authenticate(Object credentials)
{
UnitOfWork unitOfWork = module.unitOfWorkFactory().newUnitOfWork(UsecaseBuilder.newUsecase("Authenticate JMX user"));
Subject subject = null;
try
{
if (!(credentials instanceof String[]))
{
// Special case for null so we get a more informative message
if (credentials == null)
{
throw new SecurityException("Credentials required");
}
throw new SecurityException("Credentials should be String[]");
}
final String[] aCredentials = (String[]) credentials;
if (aCredentials.length != 2)
{
throw new SecurityException("Credentials should have 2 elements");
}
String username = aCredentials[0];
String password = aCredentials[1];
Authentication user = unitOfWork.get(Authentication.class, username);
if (!user.login(password))
{
throw new SecurityException("User/password combination not valid.");
}
if (((UserAuthentication.Data) user).isAdministrator())
{
subject = new Subject(true,
Collections.singleton(new JMXPrincipal(username)),
Collections.EMPTY_SET,
Collections.EMPTY_SET);
} else
{
throw new SecurityException("Invalid credentials");
}
unitOfWork.complete();
} catch (Throwable e)
{
unitOfWork.discard();
throw new SecurityException( e );
}
return subject;
}
}
}
}