Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BISERVER-14214] and [BISERVER-14215] Restrict logconfig APIs to administrator and enable by default #4377

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
</wss:service>
</wss:binding>

<!-- GWT services are defined as Spring beans. The id maps to the request URL like so:
<!-- GWT services are defined as Spring beans. The id maps to the request URL like so:
requests to "/ws/gwt/myService" will be dispatched to a bean with id "ws-gwt-myService"
-->
<bean id="ws-gwt-unifiedRepository" class="org.pentaho.platform.repository2.unified.webservices.DefaultUnifiedRepositoryWebService"/>
Expand Down Expand Up @@ -166,4 +166,5 @@

<bean class="org.pentaho.platform.web.http.api.resources.PasswordResource" scope="request"/>
<bean class="org.pentaho.platform.web.http.api.resources.ServiceResource" scope="request"/>
<bean class="org.pentaho.platform.web.http.api.resources.Log4jResource" scope="request"/>
</beans>
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* See the GNU Lesser General Public License for more details.
*
*
* Copyright (c) 2002-2018 Hitachi Vantara. All rights reserved.
* Copyright (c) 2002-2019 Hitachi Vantara. All rights reserved.
*
*/

Expand All @@ -29,6 +29,11 @@
import org.codehaus.enunciate.jaxrs.ResponseCode;
import org.codehaus.enunciate.jaxrs.StatusCodes;
import org.owasp.encoder.Encode;
import org.pentaho.platform.api.engine.IAuthorizationPolicy;
import org.pentaho.platform.engine.core.system.PentahoSystem;
import org.pentaho.platform.security.policy.rolebased.actions.AdministerSecurityAction;
import org.pentaho.platform.security.policy.rolebased.actions.RepositoryCreateAction;
import org.pentaho.platform.security.policy.rolebased.actions.RepositoryReadAction;

import javax.ws.rs.FormParam;
import javax.ws.rs.PUT;
Expand All @@ -38,66 +43,126 @@
import javax.ws.rs.core.Response;
import java.util.Enumeration;

import static javax.ws.rs.core.Response.Status.UNAUTHORIZED;


@Path( "/logconfig" )
public class Log4jResource {

private static final Logger LOGGER = Logger.getLogger( Log4jResource.class );
private static final String CONFIG = "log4j.xml";

private IAuthorizationPolicy policy;

public Log4jResource() {
init();
}

private void init() {
try {
policy = PentahoSystem.get( IAuthorizationPolicy.class );
} catch ( Exception ex ) {
LOGGER.warn( "Unable to get IAuthorizationPolicy: " + ex.getMessage() );
}
}

/**
* Reloads the log4j.xml file updating the log levels and settings.
*
* <p><b>Example Request:</b><br />
* PUT pentaho/api/logconfig/reload
* </p>
*
* @return In text/plain the word "Done" if successfully executed.
*/
@PUT
@Path ( "/reload" )
@StatusCodes( {
@ResponseCode( code = 200, condition = "Successfully reload from configuration." )
@ResponseCode( code = 200, condition = "Successfully reload from configuration." ),
@ResponseCode( code = 401, condition = "User is not an administrator and is unauthorized to perform action." )
} )
@Produces( { MediaType.TEXT_PLAIN } )
public Response reloadConfiguration() throws Exception {
LOGGER.setLevel( Level.INFO );
LOGGER.info( "Reloading configuration..." );

DOMConfigurator.configure( Loader.getResource( CONFIG ) );
return Response.ok( "Done" ).build();
if ( canAdminister() ) {
LOGGER.setLevel( Level.INFO );
LOGGER.info( "Reloading configuration..." );

DOMConfigurator.configure( Loader.getResource( CONFIG ) );
return Response.ok( "Done" ).build();
} else {
return Response.status( UNAUTHORIZED ).build();
}
}

/**
* Updates the log level for an existing log category. This change does not update the log4j.xml and does not persist across restarts.
*
* If the category parameter is provided, this will only update the level for the specific category specified. If the category parameter is not provided, this will update the log level for every logger.
*
* <p><b>Example Request:</b><br />
* PUT pentaho/api/logconfig/reload
* </p>
*
* @param category (Optional) The logging category to update the log level.
* @param level The log level to set. Valid values are (ALL, DEBUG, INFO, WARN, ERROR, FATAL, OFF, and TRACE).
*
* @return In text/plain the string "Log level updated." if successfully executed if the category is empty. In text/plain the string "Setting log level for: '${category}' to be: ${level}" if the category is not empty.
*/
@PUT
@Path ( "/update" )
@StatusCodes( {
@ResponseCode( code = 200, condition = "Successfully update log level." ),
@ResponseCode( code = 304, condition = "Log level is not modified." )
@ResponseCode( code = 304, condition = "Log level is not modified." ),
@ResponseCode( code = 401, condition = "User is not an administrator and is unauthorized to perform action." )
} )
@Produces( { MediaType.TEXT_PLAIN } )
public Response updateLogLevel( @FormParam( "level" ) String targetLevel, @FormParam( "category" ) String category ) throws Exception {
LOGGER.setLevel( Level.INFO );
public Response updateLogLevel( @FormParam( "level" ) String level, @FormParam( "category" ) String category ) throws Exception {
if ( canAdminister() ) {
LOGGER.setLevel( Level.INFO );

if ( StringUtils.isBlank( targetLevel ) && StringUtils.isBlank( category ) ) {
return Response.notModified( "No parameter provided, log level not modified." ).build();
}
if ( StringUtils.isBlank( level ) ) {
return Response.notModified( "No parameter provided, log level not modified." ).build();
}

Logger root = LogManager.getRootLogger();

Logger root = LogManager.getRootLogger();

if ( StringUtils.isNotBlank( targetLevel ) ) {
LOGGER.info( "Request to set log level: " + targetLevel );
LOGGER.info( "Request to set log level: " + level );

if ( StringUtils.isNotBlank( category ) ) {
LOGGER.info( "Request to set log level for package: " + category );
Logger catLog = LogManager.exists( category );
if ( catLog != null ) {
catLog.setLevel( Level.toLevel( targetLevel, root.getLevel() ) );
catLog.setLevel( Level.toLevel( level, root.getLevel() ) );
return Response.ok( "Setting log level for: '" + catLog.getName() + "' to be: " + catLog.getLevel() ).build();
}
return Response.notModified( "Category: '" + Encode.forHtml( category ) + "' not found, log level not modified." ).build();
return Response
.notModified( "Category: '" + Encode.forHtml( category ) + "' not found, log level not modified." ).build();
}

root.setLevel( Level.toLevel( targetLevel, root.getLevel() ) );
root.setLevel( Level.toLevel( level, root.getLevel() ) );
LOGGER.info( "Root logger level set to: " + root.getLevel() );

Enumeration e = LogManager.getCurrentLoggers();
while ( e.hasMoreElements() ) {
Logger logger = (Logger) e.nextElement();
logger.setLevel( Level.toLevel( targetLevel, root.getLevel() ) );
logger.setLevel( Level.toLevel( level, root.getLevel() ) );
}


return Response.ok( "Log level updated." ).build();
} else {
return Response.status( UNAUTHORIZED ).build();
}
}

/**
* Check if user has the rights to administrator
*
*/
private boolean canAdminister() {
return policy.isAllowed( RepositoryReadAction.NAME ) && policy.isAllowed( RepositoryCreateAction.NAME )
&& ( policy.isAllowed( AdministerSecurityAction.NAME ) );

return Response.ok( "Log level updated." ).build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* See the GNU Lesser General Public License for more details.
*
*
* Copyright (c) 2002-2018 Hitachi Vantara. All rights reserved.
* Copyright (c) 2002-2019 Hitachi Vantara. All rights reserved.
*
*/

Expand All @@ -25,22 +25,46 @@
import org.apache.log4j.Logger;
import org.apache.log4j.helpers.Loader;
import org.apache.log4j.xml.DOMConfigurator;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.pentaho.platform.api.engine.IAuthorizationPolicy;
import org.pentaho.test.platform.engine.core.MicroPlatform;

import javax.ws.rs.core.Response;
import java.util.Enumeration;
import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;

public class Log4jResourceTest {

private static final String CONFIG = "log4j.xml";
private Log4jResource target = new Log4jResource();

private MicroPlatform mp = null;

@Before
public void setUp() throws Exception {
DOMConfigurator.configure( Loader.getResource( CONFIG ) );
}

@After
public void tearDown() {
if ( mp != null ) {
mp.stop();
}
}

@Test
public void resetLogLevel() throws Exception {
// Setup the authorization
mp = new MicroPlatform();
mp.defineInstance( IAuthorizationPolicy.class, new AllowAuthorizationPolicy() );
mp.start();

Log4jResource target = new Log4jResource();

Logger root = LogManager.getRootLogger();
Level initialLevel = root.getLevel();
assertNotEquals( initialLevel, Level.ALL );
Expand All @@ -53,8 +77,36 @@ public void resetLogLevel() throws Exception {
assertEquals( "Done", res.getEntity().toString() );
}

@Test
public void resetLogLevelNotAdmin() throws Exception {
// Setup the authorization
mp = new MicroPlatform();
mp.defineInstance( IAuthorizationPolicy.class, new DenyAuthorizationPolicy() );
mp.start();

Log4jResource target = new Log4jResource();

Logger root = LogManager.getRootLogger();
Level initialLevel = root.getLevel();
assertNotEquals( initialLevel, Level.ALL );
root.setLevel( Level.ALL );
assertEquals( Level.ALL, root.getLevel() );

Response res = target.reloadConfiguration();
assertEquals( Level.ALL, LogManager.getRootLogger().getLevel() );

assertEquals( 401, res.getStatus() );
}

@Test
public void updateLogLevel() throws Exception {
// Setup the authorization
mp = new MicroPlatform();
mp.defineInstance( IAuthorizationPolicy.class, new AllowAuthorizationPolicy() );
mp.start();

Log4jResource target = new Log4jResource();

Response res = target.updateLogLevel( null, null );
assertEquals( 304, res.getStatus() );

Expand Down Expand Up @@ -87,9 +139,49 @@ public void updateLogLevel() throws Exception {
res.getMetadata().values().toArray()[0].toString() );
}

@Before
public void readFromConfig() {
DOMConfigurator.configure( Loader.getResource( CONFIG ) );
@Test
public void updateLogLevelNotAdmin() throws Exception {
// Setup the authorization
mp = new MicroPlatform();
mp.defineInstance( IAuthorizationPolicy.class, new DenyAuthorizationPolicy() );
mp.start();

Log4jResource target = new Log4jResource();

Response res = target.updateLogLevel( "DEBUG", "org.pentaho" );
assertEquals( 401, res.getStatus() );
}

class AllowAuthorizationPolicy implements IAuthorizationPolicy {

@Override
public boolean isAllowed( String actionName ) {
// TODO Auto-generated method stub
return true;
}

@Override
public List<String> getAllowedActions( String actionNamespace ) {
// TODO Auto-generated method stub
return null;
}

}

class DenyAuthorizationPolicy implements IAuthorizationPolicy {

@Override
public boolean isAllowed( String actionName ) {
// TODO Auto-generated method stub
return false;
}

@Override
public List<String> getAllowedActions( String actionNamespace ) {
// TODO Auto-generated method stub
return null;
}

}

}