/**
* Copyright (c) 2008-2011 Sonatype, Inc.
* All rights reserved. Includes the third-party code listed at http://www.sonatype.com/products/nexus/attributions.
*
* This program is free software: you can redistribute it and/or modify it only under the terms of the GNU Affero General
* Public License Version 3 as published by the Free Software Foundation.
*
* This program 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 Affero General Public License Version 3
* for more details.
*
* You should have received a copy of the GNU Affero General Public License Version 3 along with this program. If not, see
* http://www.gnu.org/licenses.
*
* Sonatype Nexus (TM) Open Source Version is available from Sonatype, Inc. Sonatype and Sonatype Nexus are trademarks of
* Sonatype, Inc. Apache Maven is a trademark of the Apache Foundation. M2Eclipse is a trademark of the Eclipse Foundation.
* All other trademarks are the property of their respective owners.
*/
package org.sonatype.nexus.rest.global;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.codehaus.enunciate.contract.jaxrs.ResourceMethodSignature;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.util.StringUtils;
import org.restlet.Context;
import org.restlet.data.Reference;
import org.restlet.data.Request;
import org.restlet.data.Response;
import org.restlet.data.Status;
import org.restlet.resource.ResourceException;
import org.restlet.resource.Variant;
import org.sonatype.configuration.ConfigurationException;
import org.sonatype.configuration.validation.InvalidConfigurationException;
import org.sonatype.micromailer.Address;
import org.sonatype.nexus.configuration.model.CRemoteAuthentication;
import org.sonatype.nexus.configuration.model.CRemoteConnectionSettings;
import org.sonatype.nexus.configuration.model.CRemoteHttpProxySettings;
import org.sonatype.nexus.configuration.model.CRestApiSettings;
import org.sonatype.nexus.configuration.model.CSmtpConfiguration;
import org.sonatype.nexus.configuration.source.ApplicationConfigurationSource;
import org.sonatype.nexus.notification.NotificationCheat;
import org.sonatype.nexus.notification.NotificationManager;
import org.sonatype.nexus.notification.NotificationTarget;
import org.sonatype.nexus.proxy.repository.UsernamePasswordRemoteAuthenticationSettings;
import org.sonatype.nexus.rest.model.ErrorReportingSettings;
import org.sonatype.nexus.rest.model.GlobalConfigurationResource;
import org.sonatype.nexus.rest.model.GlobalConfigurationResourceResponse;
import org.sonatype.nexus.rest.model.RemoteConnectionSettings;
import org.sonatype.nexus.rest.model.RemoteHttpProxySettings;
import org.sonatype.nexus.rest.model.RestApiSettings;
import org.sonatype.nexus.rest.model.SmtpSettings;
import org.sonatype.plexus.rest.resource.PathProtectionDescriptor;
import org.sonatype.plexus.rest.resource.PlexusResource;
import org.sonatype.plexus.rest.resource.PlexusResourceException;
import org.sonatype.security.SecuritySystem;
import org.sonatype.security.authentication.AuthenticationException;
import org.sonatype.security.configuration.source.SecurityConfigurationSource;
import org.sonatype.security.usermanagement.UserNotFoundException;
/**
* The GlobalConfiguration resource. It simply gets and builds the requested config REST model (DTO) and passes
* serializes it using underlying representation mechanism.
*
* @author cstamas
* @author tstevens
*/
@Component( role = PlexusResource.class, hint = "GlobalConfigurationPlexusResource" )
@Path( GlobalConfigurationPlexusResource.RESOURCE_URI )
@Produces( { "application/xml", "application/json" } )
@Consumes( { "application/xml", "application/json" } )
public class GlobalConfigurationPlexusResource
extends AbstractGlobalConfigurationPlexusResource
{
/** The config key used in URI and request attributes */
public static final String CONFIG_NAME_KEY = "configName";
public static final String RESOURCE_URI = "/global_settings/{" + CONFIG_NAME_KEY + "}";
/** Name denoting current Nexus configuration */
public static final String CURRENT_CONFIG_NAME = "current";
/** Name denoting default Nexus configuration */
public static final String DEFAULT_CONFIG_NAME = "default";
@Requirement
private SecuritySystem securitySystem;
@Requirement
private NotificationManager notificationManager;
// DEFAULT CONFIG
// ==
@Requirement( hint = "static" )
private SecurityConfigurationSource defaultSecurityConfigurationSource;
@Requirement( hint = "static" )
private ApplicationConfigurationSource configurationSource;
// ----------------------------------------------------------------------------
// Default Configuration
// ----------------------------------------------------------------------------
public boolean isDefaultSecurityEnabled()
{
return this.defaultSecurityConfigurationSource.getConfiguration().isEnabled();
}
public boolean isDefaultAnonymousAccessEnabled()
{
return this.defaultSecurityConfigurationSource.getConfiguration().isAnonymousAccessEnabled();
}
public String getDefaultAnonymousUsername()
{
return this.defaultSecurityConfigurationSource.getConfiguration().getAnonymousUsername();
}
public String getDefaultAnonymousPassword()
{
return this.defaultSecurityConfigurationSource.getConfiguration().getAnonymousPassword();
}
public List<String> getDefaultRealms()
{
return this.defaultSecurityConfigurationSource.getConfiguration().getRealms();
}
public CRemoteConnectionSettings readDefaultGlobalRemoteConnectionSettings()
{
return configurationSource.getConfiguration().getGlobalConnectionSettings();
}
public CRemoteHttpProxySettings readDefaultGlobalRemoteHttpProxySettings()
{
return configurationSource.getConfiguration().getGlobalHttpProxySettings();
}
public CRestApiSettings readDefaultRestApiSettings()
{
return configurationSource.getConfiguration().getRestApi();
}
public CSmtpConfiguration readDefaultSmtpConfiguration()
{
return configurationSource.getConfiguration().getSmtpConfiguration();
}
// ==
public GlobalConfigurationPlexusResource()
{
this.setModifiable( true );
}
@Override
public Object getPayloadInstance()
{
return new GlobalConfigurationResourceResponse();
}
@Override
public String getResourceUri()
{
return RESOURCE_URI;
}
@Override
public PathProtectionDescriptor getResourceProtection()
{
return new PathProtectionDescriptor( "/global_settings/*", "authcBasic,perms[nexus:settings]" );
}
/**
* Get the specified global configuration (i.e. current or default)
*
* @param configName The name of the config (as returned by the global configuration list resource) to get.
*/
@Override
@GET
@ResourceMethodSignature( pathParams = { @PathParam( GlobalConfigurationPlexusResource.CONFIG_NAME_KEY ) }, output = GlobalConfigurationResourceResponse.class )
public Object get( Context context, Request request, Response response, Variant variant )
throws ResourceException
{
String configurationName = request.getAttributes().get( CONFIG_NAME_KEY ).toString();
if ( !DEFAULT_CONFIG_NAME.equals( configurationName ) && !CURRENT_CONFIG_NAME.equals( configurationName ) )
{
throw new ResourceException( Status.CLIENT_ERROR_NOT_FOUND );
}
else
{
GlobalConfigurationResource resource = new GlobalConfigurationResource();
if ( DEFAULT_CONFIG_NAME.equals( configurationName ) )
{
fillDefaultConfiguration( request, resource );
}
else
{
fillCurrentConfiguration( request, resource );
}
GlobalConfigurationResourceResponse result = new GlobalConfigurationResourceResponse();
result.setData( resource );
return result;
}
}
/**
* Update the global configuration.
*
* @param configName The name of the config (as returned by the global configuration list resource) to update.
*/
@Override
@PUT
@ResourceMethodSignature( pathParams = { @PathParam( GlobalConfigurationPlexusResource.CONFIG_NAME_KEY ) }, input = GlobalConfigurationResourceResponse.class )
public Object put( Context context, Request request, Response response, Object payload )
throws ResourceException
{
String configurationName = request.getAttributes().get( CONFIG_NAME_KEY ).toString();
if ( !DEFAULT_CONFIG_NAME.equals( configurationName ) && !CURRENT_CONFIG_NAME.equals( configurationName ) )
{
throw new ResourceException( Status.CLIENT_ERROR_NOT_FOUND );
}
else if ( !CURRENT_CONFIG_NAME.equals( configurationName ) )
{
throw new ResourceException( Status.CLIENT_ERROR_METHOD_NOT_ALLOWED );
}
else
{
GlobalConfigurationResourceResponse configRequest = (GlobalConfigurationResourceResponse) payload;
if ( configRequest != null )
{
GlobalConfigurationResource resource = configRequest.getData();
try
{
if ( resource.getSmtpSettings() != null )
{
SmtpSettings settings = resource.getSmtpSettings();
getNexusEmailer().setSMTPHostname( settings.getHost() );
// lookup old password
String oldPassword = getNexusEmailer().getSMTPPassword();
getNexusEmailer().setSMTPPassword( this.getActualPassword( settings.getPassword(), oldPassword ) );
getNexusEmailer().setSMTPPort( settings.getPort() );
getNexusEmailer().setSMTPSslEnabled( settings.isSslEnabled() );
getNexusEmailer().setSMTPTlsEnabled( settings.isTlsEnabled() );
getNexusEmailer().setSMTPUsername( settings.getUsername() );
getNexusEmailer().setSMTPSystemEmailAddress(
new Address(
settings.getSystemEmailAddress().trim() ) );
}
ErrorReportingSettings settings = resource.getErrorReportingSettings();
if ( settings != null )
{
getErrorReportingManager().setEnabled( settings.isReportErrorsAutomatically() );
getErrorReportingManager().setJIRAUsername( settings.getJiraUsername() );
// look up old password
getErrorReportingManager().setJIRAPassword(
this.getActualPassword(
settings.getJiraPassword(),
getErrorReportingManager().getJIRAPassword() ) );
getErrorReportingManager().setUseGlobalProxy( true );
}
else
{
getErrorReportingManager().setEnabled( false );
getErrorReportingManager().setJIRAUsername( null );
getErrorReportingManager().setJIRAPassword( null );
}
if ( resource.getGlobalConnectionSettings() != null )
{
RemoteConnectionSettings s = resource.getGlobalConnectionSettings();
getGlobalRemoteConnectionSettings().setConnectionTimeout( s.getConnectionTimeout() * 1000 );
getGlobalRemoteConnectionSettings().setRetrievalRetryCount( s.getRetrievalRetryCount() );
getGlobalRemoteConnectionSettings().setQueryString( s.getQueryString() );
getGlobalRemoteConnectionSettings().setUserAgentCustomizationString( s.getUserAgentString() );
}
if ( resource.getGlobalHttpProxySettings() != null
&& !StringUtils.isEmpty( resource.getGlobalHttpProxySettings().getProxyHostname() ) )
{
RemoteHttpProxySettings s = resource.getGlobalHttpProxySettings();
getGlobalHttpProxySettings().setHostname( s.getProxyHostname() );
getGlobalHttpProxySettings().setPort( s.getProxyPort() );
List<String> nonProxyHosts = resource.getGlobalHttpProxySettings().getNonProxyHosts();
if ( nonProxyHosts != null && !nonProxyHosts.isEmpty() )
{
// removing nulls and empty strings
HashSet<String> cleanNonProxyHosts = new HashSet<String>();
for ( String host : nonProxyHosts )
{
if ( StringUtils.isNotEmpty( host ) )
{
cleanNonProxyHosts.add( host );
}
}
getGlobalHttpProxySettings().setNonProxyHosts( cleanNonProxyHosts );
}
else
{
// clear it out
getGlobalHttpProxySettings().setNonProxyHosts( new HashSet<String>( 0 ) );
}
if ( s.getAuthentication() != null )
{
CRemoteAuthentication auth = new CRemoteAuthentication();
auth.setUsername( s.getAuthentication().getUsername() );
String oldPassword = null;
if ( getGlobalHttpProxySettings().getProxyAuthentication() != null )
{
oldPassword =
( (UsernamePasswordRemoteAuthenticationSettings) getGlobalHttpProxySettings().getProxyAuthentication() ).getPassword();
}
auth.setPassword( this.getActualPassword( s.getAuthentication().getPassword(), oldPassword ) );
auth.setNtlmDomain( s.getAuthentication().getNtlmDomain() );
auth.setNtlmHost( s.getAuthentication().getNtlmHost() );
// auth.setPrivateKey( s.getAuthentication().getPrivateKey() );
// auth.setPassphrase( s.getAuthentication().getPassphrase() );
getGlobalHttpProxySettings().setProxyAuthentication(
getAuthenticationInfoConverter().convertAndValidateFromModel(
auth ) );
}
else
{
getGlobalHttpProxySettings().setProxyAuthentication( null );
}
}
else
{
getGlobalHttpProxySettings().disable();
}
getNexusConfiguration().setRealms( resource.getSecurityRealms() );
getNexusConfiguration().setSecurityEnabled( resource.isSecurityEnabled() );
getNexusConfiguration().setAnonymousAccessEnabled( resource.isSecurityAnonymousAccessEnabled() );
if ( resource.isSecurityAnonymousAccessEnabled()
&& !StringUtils.isEmpty( resource.getSecurityAnonymousUsername() )
&& !StringUtils.isEmpty( resource.getSecurityAnonymousPassword() ) )
{
// check if the user/pass changed
String oldPassword = getNexusConfiguration().getAnonymousPassword();
String newPassword =
this.getActualPassword( resource.getSecurityAnonymousPassword(), oldPassword );
if ( !StringUtils.equals( getNexusConfiguration().getAnonymousUsername(),
resource.getSecurityAnonymousUsername() )
|| !StringUtils.equals( newPassword, oldPassword ) )
{
// test auth
try
{
// try to "log in" with supplied credentials
// the anon user a) should exists b) the pwd must work
securitySystem.getUser( resource.getSecurityAnonymousUsername() );
securitySystem.authenticate( new UsernamePasswordToken(
resource.getSecurityAnonymousUsername(),
newPassword ) );
}
catch ( UserNotFoundException e )
{
getLogger().warn(
"Nexus refused to apply configuration, the supplied anonymous information is wrong.",
e );
String msg = "User '" + resource.getSecurityAnonymousUsername() + "' does not exist.";
throw new PlexusResourceException( Status.CLIENT_ERROR_BAD_REQUEST, msg,
getNexusErrorResponse( "securityAnonymousUsername",
msg ) );
}
catch ( AuthenticationException e )
{
// the supplied anon auth info is wrong
getLogger().warn(
"Nexus refused to apply configuration, the supplied anonymous information is wrong.",
e );
String msg =
"The password of user '" + resource.getSecurityAnonymousUsername()
+ "' is incorrect.";
throw new PlexusResourceException( Status.CLIENT_ERROR_BAD_REQUEST, msg,
getNexusErrorResponse( "securityAnonymousPassword",
msg ) );
}
}
getNexusConfiguration().setAnonymousUsername( resource.getSecurityAnonymousUsername() );
getNexusConfiguration().setAnonymousPassword( newPassword );
}
else if ( resource.isSecurityAnonymousAccessEnabled() )
{
// the supplied anon auth info is wrong
getLogger().warn(
"Nexus refused to apply configuration, the supplied anonymous username/pwd information is empty." );
throw new PlexusResourceException(
Status.CLIENT_ERROR_BAD_REQUEST,
getNexusErrorResponse( "securityAnonymousUsername",
"Cannot be empty when Anonynous access is enabled" ) );
}
if ( resource.getGlobalRestApiSettings() != null )
{
RestApiSettings restApiSettings = resource.getGlobalRestApiSettings();
getGlobalRestApiSettings().setForceBaseUrl( restApiSettings.isForceBaseUrl() );
if ( StringUtils.isEmpty( resource.getGlobalRestApiSettings().getBaseUrl() ) )
{
getGlobalRestApiSettings().setBaseUrl( null );
}
else
{
getGlobalRestApiSettings().setBaseUrl(
new Reference( restApiSettings.getBaseUrl() ).getTargetRef().toString() );
}
getGlobalRestApiSettings().setUITimeout( restApiSettings.getUiTimeout() * 1000 );
}
else
{
getGlobalRestApiSettings().disable();
}
if ( resource.getSystemNotificationSettings() != null )
{
notificationManager.setEnabled( resource.getSystemNotificationSettings().isEnabled() );
NotificationTarget target = notificationManager.readNotificationTarget( NotificationCheat.AUTO_BLOCK_NOTIFICATION_GROUP_ID );
if ( target == null )
{
target = new NotificationTarget();
target.setTargetId( NotificationCheat.AUTO_BLOCK_NOTIFICATION_GROUP_ID );
}
target.getTargetRoles().clear();
target.getTargetRoles().addAll( resource.getSystemNotificationSettings().getRoles() );
target.getExternalTargets().clear();
if ( StringUtils.isNotEmpty( resource.getSystemNotificationSettings().getEmailAddresses() ) )
{
target.getExternalTargets().addAll( Arrays.asList( resource.getSystemNotificationSettings().getEmailAddresses().split( "," ) ) );
}
target.getTargetUsers().clear();
notificationManager.updateNotificationTarget( target );
}
// NEXUS-3064: to "inform" global remote storage context (and hence, all affected proxy
// repositories) about the change, but only if config is saved okay
// TODO: this is wrong, the config framework should "tell" this changed, but we have some
// design flaw here: the globalRemoteStorageContext is NOT a component, while the settings are
boolean remoteConnectionSettingsIsDirty = getGlobalRemoteConnectionSettings().isDirty();
boolean remoteHttpProxySettingsIsDirty = getGlobalHttpProxySettings().isDirty();
getNexusConfiguration().saveConfiguration();
// NEXUS-3064: to "inform" global remote storage context (and hence, all affected proxy
// repositories) about the change, but only if config is saved okay
// TODO: this is wrong, the config framework should "tell" this changed, but we have some
// design flaw here: the globalRemoteStorageContext is NOT a component, while the settings are
if ( remoteConnectionSettingsIsDirty )
{
getNexusConfiguration().getGlobalRemoteStorageContext().setRemoteConnectionSettings(
getGlobalRemoteConnectionSettings() );
}
if ( remoteHttpProxySettingsIsDirty )
{
getNexusConfiguration().getGlobalRemoteStorageContext().setRemoteProxySettings(
getGlobalHttpProxySettings() );
}
}
catch ( IOException e )
{
getLogger().warn( "Got IO Exception during update of Nexus configuration.", e );
throw new ResourceException( Status.SERVER_ERROR_INTERNAL );
}
catch ( InvalidConfigurationException e )
{
// TODO: this should be removed from the Global config, as it is NO longer part of the nexus.xml
getLogger().debug( "Configuraiton Exception while setting security values", e );
this.handleInvalidConfigurationException( e );
}
catch ( ConfigurationException e )
{
getLogger().warn( "Nexus refused to apply configuration.", e );
throw new PlexusResourceException( Status.CLIENT_ERROR_BAD_REQUEST, e.getMessage(),
getNexusErrorResponse( "*", e.getMessage() ) );
}
}
}
// TODO: this method needs some serious cleaning up...
response.setStatus( Status.SUCCESS_NO_CONTENT );
return null;
}
/**
* Externalized Nexus object to DTO's conversion, using default Nexus configuration.
*
* @param resource
*/
protected void fillDefaultConfiguration( Request request, GlobalConfigurationResource resource )
{
resource.setSecurityEnabled( isDefaultSecurityEnabled() );
resource.setSecurityAnonymousAccessEnabled( isDefaultAnonymousAccessEnabled() );
resource.setSecurityRealms( getDefaultRealms() );
resource.setSecurityAnonymousUsername( getDefaultAnonymousUsername() );
resource.setSecurityAnonymousPassword( PASSWORD_PLACE_HOLDER );
resource.setGlobalConnectionSettings( convert( readDefaultGlobalRemoteConnectionSettings() ) );
resource.setGlobalHttpProxySettings( convert( readDefaultGlobalRemoteHttpProxySettings() ) );
RestApiSettings restApiSettings = convert( readDefaultRestApiSettings() );
if ( restApiSettings != null )
{
restApiSettings.setBaseUrl( getContextRoot( request ).getTargetRef().toString() );
}
resource.setGlobalRestApiSettings( restApiSettings );
resource.setSmtpSettings( convert( readDefaultSmtpConfiguration() ) );
}
/**
* Externalized Nexus object to DTO's conversion, using current Nexus configuration.
*
* @param resource
*/
protected void fillCurrentConfiguration( Request request, GlobalConfigurationResource resource )
{
resource.setSecurityEnabled( getNexusConfiguration().isSecurityEnabled() );
resource.setSecurityAnonymousAccessEnabled( getNexusConfiguration().isAnonymousAccessEnabled() );
resource.setSecurityRealms( getNexusConfiguration().getRealms() );
resource.setSecurityAnonymousUsername( getNexusConfiguration().getAnonymousUsername() );
resource.setSecurityAnonymousPassword( PASSWORD_PLACE_HOLDER );
resource.setGlobalConnectionSettings( convert( getGlobalRemoteConnectionSettings() ) );
resource.setGlobalHttpProxySettings( convert( getGlobalHttpProxySettings() ) );
RestApiSettings restApiSettings = convert( getGlobalRestApiSettings() );
if ( restApiSettings != null && StringUtils.isEmpty( restApiSettings.getBaseUrl() ) )
{
restApiSettings.setBaseUrl( getContextRoot( request ).getTargetRef().toString() );
}
resource.setGlobalRestApiSettings( restApiSettings );
resource.setSmtpSettings( convert( getNexusEmailer() ) );
resource.setErrorReportingSettings( convert( getErrorReportingManager() ) );
resource.setSystemNotificationSettings( convert( notificationManager ) );
}
protected String getSecurityConfiguration( boolean enabled, String authSourceType )
{
if ( !enabled )
{
return SECURITY_OFF;
}
else
{
if ( SECURITY_SIMPLE.equals( authSourceType ) )
{
return SECURITY_SIMPLE;
}
else
{
return SECURITY_CUSTOM;
}
}
}
}