Skip to content

Commit

Permalink
roboconf#632 User authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
vincent-zurczak committed Apr 6, 2017
1 parent 11b3c25 commit 8d04c81
Show file tree
Hide file tree
Showing 15 changed files with 825 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ public interface UrlConstants {
String TARGETS = "targets";
String PREFERENCES = "preferences";
String SCHEDULER = "scheduler";
String AUTHENTICATION = "auth";
}
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,10 @@ public String login( String user, String pwd ) {
/**
* Determines whether a session is valid.
* @param token a token
* @param validityPeriod the validity period for a session (in seconds)
* @param validityPeriod the validity period for a session (in seconds, < 0 for unbound)
* @return true if the session is valid, false otherwise
*/
public boolean isSessionValid( final String token, int validityPeriod ) {
public boolean isSessionValid( final String token, long validityPeriod ) {

boolean valid = false;
Long loginTime = null;
Expand Down Expand Up @@ -152,7 +152,7 @@ public boolean isSessionValid( final String token, int validityPeriod ) {
* No error is thrown if the session was already invalid.
* </p>
*
* @param token a token
* @param token a token (can be null)
*/
public void logout( String token ) {
if( token != null )
Expand Down
3 changes: 3 additions & 0 deletions core/roboconf-dm-rest-services/metadata.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@

<properties pid="net.roboconf.dm.rest.services.configuration">
<property name="enable-cors" method="setEnableCors" value="false" />
<property name="enable-authentication" method="setEnableAuthentication" value="false" />
<property name="authentication-realm" method="setAuthenticationRealm" value="karaf" />
<property name="session-period" method="setSessionPeriod" value="-1" />
</properties>
</component>

Expand Down
4 changes: 2 additions & 2 deletions core/roboconf-dm-rest-services/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,9 @@
</dependency>

<dependency>
<groupId>org.apache.felix</groupId>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
<version>1.4.0</version>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,14 @@
import com.sun.jersey.api.core.ResourceConfig;

import net.roboconf.dm.management.Manager;
import net.roboconf.dm.rest.commons.security.AuthenticationManager;
import net.roboconf.dm.rest.services.cors.ResponseCorsFilter;
import net.roboconf.dm.rest.services.internal.resources.IApplicationResource;
import net.roboconf.dm.rest.services.internal.resources.IDebugResource;
import net.roboconf.dm.rest.services.internal.resources.IPreferencesResource;
import net.roboconf.dm.rest.services.internal.resources.ITargetResource;
import net.roboconf.dm.rest.services.internal.resources.impl.ApplicationResource;
import net.roboconf.dm.rest.services.internal.resources.impl.AuthenticationResource;
import net.roboconf.dm.rest.services.internal.resources.impl.DebugResource;
import net.roboconf.dm.rest.services.internal.resources.impl.ManagementResource;
import net.roboconf.dm.rest.services.internal.resources.impl.PreferencesResource;
Expand All @@ -58,6 +60,7 @@ public class RestApplication extends DefaultResourceConfig {
private final IPreferencesResource preferencesResource;
private final ManagementResource managementResource;
private final SchedulerResource schedulerResource;
private final AuthenticationResource authenticationResource;


/**
Expand All @@ -73,6 +76,7 @@ public RestApplication( Manager manager ) {
this.targetResource = new TargetResource( manager );
this.preferencesResource = new PreferencesResource( manager );
this.schedulerResource = new SchedulerResource();
this.authenticationResource = new AuthenticationResource();

getFeatures().put( "com.sun.jersey.api.json.POJOMappingFeature", Boolean.TRUE );
getFeatures().put( ResourceConfig.FEATURE_DISABLE_WADL, Boolean.TRUE );
Expand Down Expand Up @@ -100,6 +104,7 @@ public Set<Object> getSingletons() {
set.add( this.targetResource );
set.add( this.preferencesResource );
set.add( this.schedulerResource );
set.add( this.authenticationResource );

return set;
}
Expand All @@ -123,6 +128,15 @@ public void setMavenResolver( MavenResolver mavenResolver ) {
}


/**
* Sets the authentication manager.
* @param authenticationMngr the authentication manager (can be null)
*/
public void setAuthenticationManager( AuthenticationManager authenticationMngr ) {
this.authenticationResource.setAuthenticationManager( authenticationMngr );
}


/**
* Enables or disables CORS.
* @param enableCors true to enable it
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,19 @@
import java.util.Hashtable;
import java.util.logging.Logger;

import javax.servlet.Filter;

import org.ops4j.pax.url.mvn.MavenResolver;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.http.HttpService;

import com.sun.jersey.spi.container.servlet.ServletContainer;

import net.roboconf.core.utils.Utils;
import net.roboconf.dm.management.Manager;
import net.roboconf.dm.rest.commons.security.AuthenticationManager;
import net.roboconf.dm.rest.services.internal.filters.AuthenticationFilter;
import net.roboconf.dm.rest.services.internal.icons.IconServlet;
import net.roboconf.dm.rest.services.internal.websocket.RoboconfWebSocketServlet;
import net.roboconf.dm.scheduler.IScheduler;
Expand All @@ -61,13 +67,31 @@ public class ServletRegistrationComponent {
private Manager manager;
private IScheduler scheduler;
private MavenResolver mavenResolver;

private boolean enableCors = false;
private boolean enableAuthentication = false;
private long sessionPeriod;

// Internal fields
private final Logger logger = Logger.getLogger( getClass().getName());
private final BundleContext bundleContext;

RestApplication app;
ServletContainer jerseyServlet;

ServiceRegistration<Filter> filterServiceRegistration;
AuthenticationFilter authenticationFilter;
AuthenticationManager authenticationMngr;


/**
* Constructor.
* @param bundleContext
*/
public ServletRegistrationComponent( BundleContext bundleContext ) {
this.bundleContext = bundleContext;
}


/**
* The method to use when all the dependencies are resolved.
Expand Down Expand Up @@ -107,6 +131,16 @@ public void starting() throws Exception {

RoboconfWebSocketServlet websocketServlet = new RoboconfWebSocketServlet();
this.httpService.registerServlet( WEBSOCKET_CONTEXT, websocketServlet, initParams, null );

// Register a filter for authentication
this.authenticationFilter = new AuthenticationFilter();
this.authenticationFilter.setAuthenticationEnabled( this.enableAuthentication );
this.authenticationFilter.setAuthenticationMngr( this.authenticationMngr );
this.authenticationFilter.setSessionPeriod( this.sessionPeriod );

initParams = new Hashtable<> ();
initParams.put( "urlPatterns", "*" );
this.filterServiceRegistration = this.bundleContext.registerService( Filter.class, this.authenticationFilter, initParams );
}


Expand All @@ -116,6 +150,10 @@ public void starting() throws Exception {
*/
public void stopping() throws Exception {

// Remove the filter
if( this.filterServiceRegistration != null )
this.filterServiceRegistration.unregister();

// Update the HTTP service
this.logger.fine( "iPojo unregisters REST and icons servlets related to Roboconf's DM." );
if( this.httpService != null ) {
Expand All @@ -130,6 +168,8 @@ public void stopping() throws Exception {
// Reset the application
this.app = null;
this.jerseyServlet = null;
this.filterServiceRegistration = null;
this.authenticationFilter = null;
}


Expand Down Expand Up @@ -223,6 +263,52 @@ public void setEnableCors( boolean enableCors ) {
}


/**
* Invoked by iPojo.
* @param enableAuthentication the enableAuthentication to set
*/
public void setEnableAuthentication( boolean enableAuthentication ) {

this.logger.fine( "Authentication is now " + (enableAuthentication ? "enabled" : "disabled") + ". Updating the REST resource." );
this.enableAuthentication = enableAuthentication;

if( this.authenticationFilter != null )
this.authenticationFilter.setAuthenticationEnabled( enableAuthentication );
}


/**
* @param authenticationRealm the authenticationRealm to set
*/
public void setAuthenticationRealm( String authenticationRealm ) {

// Given the way sessions are stored in AuthenticationManager (private map),
// changing the realm will invalidate all the current sessions
this.logger.fine( "New authentication realm: " + authenticationRealm );
this.authenticationMngr = new AuthenticationManager( authenticationRealm );

// Propagate the change
if( this.authenticationFilter != null )
this.authenticationFilter.setAuthenticationMngr( this.authenticationMngr );

if( this.app != null )
this.app.setAuthenticationManager( this.authenticationMngr );
}


/**
* @param sessionPeriod the sessionPeriod to set
*/
public void setSessionPeriod( long sessionPeriod ) {

this.logger.fine( "New session period: " + sessionPeriod );
this.sessionPeriod = sessionPeriod;

if( this.authenticationFilter != null )
this.authenticationFilter.setSessionPeriod( sessionPeriod );
}


// These setters are not used by iPojo.
// But they may be useful when using this class outside OSGi.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/**
* Copyright 2017 Linagora, Université Joseph Fourier, Floralis
*
* The present code is developed in the scope of the joint LINAGORA -
* Université Joseph Fourier - Floralis research program and is designated
* as a "Result" pursuant to the terms and conditions of the LINAGORA
* - Université Joseph Fourier - Floralis research program. Each copyright
* holder of Results enumerated here above fully & independently holds complete
* ownership of the complete Intellectual Property rights applicable to the whole
* of said Results, and may freely exploit it in any manner which does not infringe
* the moral rights of the other copyright holders.
*
* Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* 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 net.roboconf.dm.rest.services.internal.filters;

import java.io.IOException;
import java.util.logging.Logger;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.roboconf.core.utils.Utils;
import net.roboconf.dm.rest.commons.security.AuthenticationManager;
import net.roboconf.dm.rest.services.internal.resources.IAuthenticationResource;

/**
* A filter to determine and request (if necessary) authentication.
* <p>
* This filter is registered as an OSGi service. PAX's web extender automatically
* binds it to the web server (Karaf's Jetty). This filter is only applied to the
* resources in this bundle, which means the REST API and the web socket. Other web
* applications are not impacted. As an example, Karaf and Roboconf web administrations
* are served by other bundles, this filter cannot be applied to them.
* </p>
* @author Vincent Zurczak - Linagora
*/
public class AuthenticationFilter implements Filter {

public static final String SESSION_ID = "sid";
private final Logger logger = Logger.getLogger( getClass().getName());

private AuthenticationManager authenticationMngr;
private boolean authenticationEnabled;
private long sessionPeriod;


@Override
public void doFilter( ServletRequest req, ServletResponse resp, FilterChain chain )
throws IOException, ServletException {

if( ! this.authenticationEnabled ) {
chain.doFilter( req, resp );

} else {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
String requestedPath = request.getRequestURI();
this.logger.info( "Path for auth: " + requestedPath );

// Find the session ID in the cookies
String sessionId = null;
Cookie[] cookies = request.getCookies();
if( cookies != null ) {
for( Cookie cookie : cookies ) {
if( SESSION_ID.equals( cookie.getName())) {
sessionId = cookie.getValue();
break;
}
}
}

// Is there a valid session?
boolean loggedIn = false;
if( ! Utils.isEmptyOrWhitespaces( sessionId )) {
loggedIn = this.authenticationMngr.isSessionValid( sessionId, this.sessionPeriod );
this.logger.finest( "Session " + sessionId + (loggedIn ? " was successfully " : " failed to be ") + "validated." );
} else {
this.logger.finest( "No session ID was found in the cookie. Authentication cannot be performed." );
}

// Valid session, go on. Send an error otherwise.
// No redirection, we mainly deal with our web socket and REST API.
boolean loginRequest = IAuthenticationResource.LOGIN_PATH.equals( requestedPath );
if( loggedIn || loginRequest ) {
chain.doFilter( request, response );
} else {
response.sendError( 403, "Authentication is required." );
}
}
}


@Override
public void destroy() {
// nothing
}


@Override
public void init( FilterConfig filterConfig ) throws ServletException {
// nothing
}


/**
* @param authenticationEnabled the authenticationEnabled to set
*/
public void setAuthenticationEnabled( boolean authenticationEnabled ) {
this.authenticationEnabled = authenticationEnabled;
}


/**
* @param authenticationMngr the authenticationMngr to set
*/
public void setAuthenticationMngr( AuthenticationManager authenticationMngr ) {
this.authenticationMngr = authenticationMngr;
}


/**
* @param sessionPeriod the sessionPeriod to set
*/
public void setSessionPeriod( long sessionPeriod ) {
this.sessionPeriod = sessionPeriod;
}
}
Loading

0 comments on commit 8d04c81

Please sign in to comment.