From 11b3c25728deb39cc70279159c93928ecb56c692 Mon Sep 17 00:00:00 2001
From: Vincent Zurczak
Date: Thu, 2 Mar 2017 17:22:00 +0100
Subject: [PATCH 1/3] #632 User authentication
The hardest part is done, integrate with Karaf's JAAS implementation.
Next step: implement a servlet filter that uses it
---
core/roboconf-dm-rest-commons/pom.xml | 8 +-
.../security/AuthenticationManager.java | 242 ++++++++++++++++++
.../security/AuthenticationManagerTest.java | 134 ++++++++++
3 files changed, 383 insertions(+), 1 deletion(-)
create mode 100644 core/roboconf-dm-rest-commons/src/main/java/net/roboconf/dm/rest/commons/security/AuthenticationManager.java
create mode 100644 core/roboconf-dm-rest-commons/src/test/java/net/roboconf/dm/rest/commons/security/AuthenticationManagerTest.java
diff --git a/core/roboconf-dm-rest-commons/pom.xml b/core/roboconf-dm-rest-commons/pom.xml
index 7fabad1c..82930807 100644
--- a/core/roboconf-dm-rest-commons/pom.xml
+++ b/core/roboconf-dm-rest-commons/pom.xml
@@ -86,7 +86,13 @@
roboconf-dm
${project.version}
test
-
+
+
+
+ org.mockito
+ mockito-core
+ test
+
diff --git a/core/roboconf-dm-rest-commons/src/main/java/net/roboconf/dm/rest/commons/security/AuthenticationManager.java b/core/roboconf-dm-rest-commons/src/main/java/net/roboconf/dm/rest/commons/security/AuthenticationManager.java
new file mode 100644
index 00000000..04dff006
--- /dev/null
+++ b/core/roboconf-dm-rest-commons/src/main/java/net/roboconf/dm/rest/commons/security/AuthenticationManager.java
@@ -0,0 +1,242 @@
+/**
+ * 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.commons.security;
+
+import java.io.IOException;
+import java.util.Date;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Logger;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+/**
+ * A class in charge of managing authentication and sessions.
+ *
+ * Authentication is delegated to various implementations.
+ * By default, it is handled by Karaf's JAAS implementation, but it is possible
+ * to override it by using {@link #setAuthService(IAuthService)}. You will HAVE
+ * TO use this method if you run the REST services outside Karaf.
+ *
+ *
+ * When the authentication succeeds, a token is generated by this class
+ * (a random UUID in fact). The token is stored by this class and associated
+ * with the login time.
+ *
+ *
+ * Since sessions can be limited in time (depending on admin preferences),
+ * we can verify on every action that the session is still valid.
+ *
+ *
+ * To prevent "man in the middle" "attacks, authentication should be
+ * used along with HTTPS.
+ *
+ *
+ * @author Vincent Zurczak - Linagora
+ */
+public class AuthenticationManager {
+
+ private final ConcurrentHashMap tokenToLoginTime = new ConcurrentHashMap<> ();
+ private final Logger logger = Logger.getLogger( getClass().getName());
+ private final String realm;
+
+ private IAuthService authService;
+
+
+
+ /**
+ * Constructor.
+ * @param realm
+ */
+ public AuthenticationManager( String realm ) {
+ this.realm = realm;
+ this.authService = new KarafAuthService();
+ this.authService.setRealm( this.realm );
+ }
+
+
+ /**
+ * @param authenticater the authService to set
+ */
+ public void setAuthService( IAuthService authService ) {
+ this.authService = authService;
+ authService.setRealm( this.realm );
+ }
+
+
+ /**
+ * Authenticates a user and creates a new session.
+ * @param user a user name
+ * @param pwd a pass word
+ * @return a token if authentication worked, null if it failed
+ */
+ public String login( String user, String pwd ) {
+
+ String token = null;
+ try {
+ this.authService.authenticate( user, pwd );
+
+ token = UUID.randomUUID().toString();
+ Long now = new Date().getTime();
+ this.tokenToLoginTime.put( token, now );
+
+ } catch( LoginException e ) {
+ this.logger.severe( "Invalid login attempt by user " + user );
+ }
+
+ return token;
+ }
+
+
+ /**
+ * Determines whether a session is valid.
+ * @param token a token
+ * @param validityPeriod the validity period for a session (in seconds)
+ * @return true if the session is valid, false otherwise
+ */
+ public boolean isSessionValid( final String token, int validityPeriod ) {
+
+ boolean valid = false;
+ Long loginTime = null;
+ if( token != null )
+ loginTime = this.tokenToLoginTime.get( token );
+
+ if( validityPeriod < 0 ) {
+ valid = loginTime != null;
+
+ } else if( loginTime != null ) {
+ long now = new Date().getTime();
+ valid = (now - loginTime) <= validityPeriod * 1000;
+
+ // Invalid sessions should be deleted
+ if( ! valid )
+ logout( token );
+ }
+
+ return valid;
+ }
+
+
+ /**
+ * Invalidates a session.
+ *
+ * No error is thrown if the session was already invalid.
+ *
+ *
+ * @param token a token
+ */
+ public void logout( String token ) {
+ if( token != null )
+ this.tokenToLoginTime.remove( token );
+ }
+
+
+ /**
+ * An abstraction to manage authentication.
+ * @author Vincent Zurczak - Linagora
+ */
+ public interface IAuthService {
+
+ /**
+ * Authenticates someone by user and password.
+ * @param user a user name
+ * @param pwd a password
+ * @throws LoginException if authentication failed
+ */
+ void authenticate( String user, String pwd ) throws LoginException;
+
+ /**
+ * Sets the REALM to use.
+ * @param realm a realm name
+ */
+ void setRealm( String realm );
+ }
+
+
+ /**
+ * Authentication managed by Apache Karaf.
+ *
+ * Karaf uses JAAS and by default supports several login modules
+ * (properties files, databases, LDAP, etc).
+ *
+ * @author Vincent Zurczak - Linagora
+ */
+ public static class KarafAuthService implements IAuthService {
+ private String realm;
+
+
+ @Override
+ public void authenticate( String user, String pwd ) throws LoginException {
+ LoginContext loginCtx = new LoginContext( this.realm, new RoboconfCallbackHandler( user, pwd ));
+ loginCtx.login();
+ }
+
+ @Override
+ public void setRealm( String realm ) {
+ this.realm = realm;
+ }
+ }
+
+
+ /**
+ * A callback handler for JAAS.
+ * @author Vincent Zurczak - Linagora
+ */
+ static final class RoboconfCallbackHandler implements CallbackHandler {
+ private final String username, password;
+
+
+ /**
+ * Constructor.
+ * @param username
+ * @param password
+ */
+ public RoboconfCallbackHandler( String username, String password ) {
+ this.username = username;
+ this.password = password;
+ }
+
+
+ @Override
+ public void handle( Callback[] callbacks ) throws IOException, UnsupportedCallbackException {
+
+ for( Callback callback : callbacks ) {
+ if (callback instanceof NameCallback )
+ ((NameCallback) callback).setName( this.username );
+ else if( callback instanceof PasswordCallback )
+ ((PasswordCallback) callback).setPassword( this.password.toCharArray());
+ else
+ throw new UnsupportedCallbackException( callback );
+ }
+ }
+ }
+}
diff --git a/core/roboconf-dm-rest-commons/src/test/java/net/roboconf/dm/rest/commons/security/AuthenticationManagerTest.java b/core/roboconf-dm-rest-commons/src/test/java/net/roboconf/dm/rest/commons/security/AuthenticationManagerTest.java
new file mode 100644
index 00000000..028632d0
--- /dev/null
+++ b/core/roboconf-dm-rest-commons/src/test/java/net/roboconf/dm/rest/commons/security/AuthenticationManagerTest.java
@@ -0,0 +1,134 @@
+/**
+ * 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.commons.security;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.LanguageCallback;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginException;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import net.roboconf.dm.rest.commons.security.AuthenticationManager.IAuthService;
+import net.roboconf.dm.rest.commons.security.AuthenticationManager.RoboconfCallbackHandler;
+
+/**
+ * @author Vincent Zurczak - Linagora
+ */
+public class AuthenticationManagerTest {
+
+ @Test
+ public void testAuthenticationChain_success() {
+
+ AuthenticationManager mngr = new AuthenticationManager( "realm" );
+ IAuthService authService = Mockito.mock( IAuthService.class );
+ mngr.setAuthService( authService );
+
+ String token = mngr.login( "me", "my password" );
+ Assert.assertNotNull( token );
+ Assert.assertTrue( mngr.isSessionValid( token, 1 ));
+ Assert.assertTrue( mngr.isSessionValid( token, -1 ));
+
+ mngr.logout( token );
+ Assert.assertFalse( mngr.isSessionValid( token, 1 ));
+ Assert.assertFalse( mngr.isSessionValid( token, -1 ));
+ }
+
+
+ @Test
+ public void testAuthenticationChain_failure() throws Exception {
+
+ AuthenticationManager mngr = new AuthenticationManager( "realm" );
+ IAuthService authService = Mockito.mock( IAuthService.class );
+ Mockito.doThrow( new LoginException( "for test" )).when( authService ).authenticate( Mockito.anyString(), Mockito.anyString());
+ mngr.setAuthService( authService );
+
+ String token = mngr.login( "me", "my password" );
+ Assert.assertNull( token );
+ Assert.assertFalse( mngr.isSessionValid( token, 1 ));
+ Assert.assertFalse( mngr.isSessionValid( token, -1 ));
+
+ mngr.logout( token );
+ Assert.assertFalse( mngr.isSessionValid( token, 1 ));
+ Assert.assertFalse( mngr.isSessionValid( token, -1 ));
+ }
+
+
+ @Test
+ public void testAuthenticationChain_validityPeriodExpired() throws Exception {
+
+ AuthenticationManager mngr = new AuthenticationManager( "realm" );
+ IAuthService authService = Mockito.mock( IAuthService.class );
+ mngr.setAuthService( authService );
+
+ String token = mngr.login( "me", "my password" );
+ Assert.assertNotNull( token );
+ Assert.assertTrue( mngr.isSessionValid( token, 1 ));
+ Thread.sleep( 1020 );
+ Assert.assertFalse( mngr.isSessionValid( token, 1 ));
+
+ // The session was removed, it should not be marked as valid anymore
+ Assert.assertFalse( mngr.isSessionValid( token, 10 ));
+ }
+
+
+ @Test
+ public void testAuthenticationChain_withKaraf_butOutsideKaraf() throws Exception {
+
+ AuthenticationManager mngr = new AuthenticationManager( "realm" );
+
+ String token = mngr.login( "me", "my password" );
+ Assert.assertNull( token );
+ Assert.assertFalse( mngr.isSessionValid( token, -1 ));
+ }
+
+
+ @Test
+ public void testRoboconfCallbackHandler_success() throws Exception {
+
+ RoboconfCallbackHandler handler = new RoboconfCallbackHandler( "user", "password" );
+ handler.handle( new Callback[] {
+ new NameCallback( "Username: " ),
+ new PasswordCallback( "Password: ", false )
+ });
+ }
+
+
+ @Test( expected = UnsupportedCallbackException.class )
+ public void testRoboconfCallbackHandler_failure() throws Exception {
+
+ RoboconfCallbackHandler handler = new RoboconfCallbackHandler( "user", "password" );
+ handler.handle( new Callback[] {
+ new NameCallback( "Username: " ),
+ new PasswordCallback( "Password: ", false ),
+ new LanguageCallback()
+ });
+ }
+}
From 8d04c817228647dc4801d5ad3445a2e7b01d147a Mon Sep 17 00:00:00 2001
From: Vincent Zurczak
Date: Tue, 4 Apr 2017 19:28:15 +0200
Subject: [PATCH 2/3] #632 User authentication
---
.../dm/rest/commons/UrlConstants.java | 1 +
.../security/AuthenticationManager.java | 6 +-
core/roboconf-dm-rest-services/metadata.xml | 3 +
core/roboconf-dm-rest-services/pom.xml | 4 +-
.../services/internal/RestApplication.java | 14 ++
.../ServletRegistrationComponent.java | 86 +++++++++
.../filters/AuthenticationFilter.java | 146 ++++++++++++++
.../resources/IAuthenticationResource.java | 71 +++++++
.../impl/AuthenticationResource.java | 88 +++++++++
.../ServletRegistrationComponentTest.java | 156 ++++++++++++---
.../filters/AuthenticationFilterTest.java | 179 ++++++++++++++++++
.../impl/AuthenticationResourceTest.java | 84 ++++++++
.../impl/ManagementResourceTest.java | 8 +
...oboconf.dm.rest.services.configuration.cfg | 12 ++
.../resources/etc/org.ops4j.pax.logging.cfg | 1 +
15 files changed, 825 insertions(+), 34 deletions(-)
create mode 100644 core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/filters/AuthenticationFilter.java
create mode 100644 core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/resources/IAuthenticationResource.java
create mode 100644 core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/resources/impl/AuthenticationResource.java
create mode 100644 core/roboconf-dm-rest-services/src/test/java/net/roboconf/dm/rest/services/internal/filters/AuthenticationFilterTest.java
create mode 100644 core/roboconf-dm-rest-services/src/test/java/net/roboconf/dm/rest/services/internal/resources/impl/AuthenticationResourceTest.java
diff --git a/core/roboconf-dm-rest-commons/src/main/java/net/roboconf/dm/rest/commons/UrlConstants.java b/core/roboconf-dm-rest-commons/src/main/java/net/roboconf/dm/rest/commons/UrlConstants.java
index a2a00bcf..de3549d0 100644
--- a/core/roboconf-dm-rest-commons/src/main/java/net/roboconf/dm/rest/commons/UrlConstants.java
+++ b/core/roboconf-dm-rest-commons/src/main/java/net/roboconf/dm/rest/commons/UrlConstants.java
@@ -36,4 +36,5 @@ public interface UrlConstants {
String TARGETS = "targets";
String PREFERENCES = "preferences";
String SCHEDULER = "scheduler";
+ String AUTHENTICATION = "auth";
}
diff --git a/core/roboconf-dm-rest-commons/src/main/java/net/roboconf/dm/rest/commons/security/AuthenticationManager.java b/core/roboconf-dm-rest-commons/src/main/java/net/roboconf/dm/rest/commons/security/AuthenticationManager.java
index 04dff006..d257bc92 100644
--- a/core/roboconf-dm-rest-commons/src/main/java/net/roboconf/dm/rest/commons/security/AuthenticationManager.java
+++ b/core/roboconf-dm-rest-commons/src/main/java/net/roboconf/dm/rest/commons/security/AuthenticationManager.java
@@ -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;
@@ -152,7 +152,7 @@ public boolean isSessionValid( final String token, int validityPeriod ) {
* No error is thrown if the session was already invalid.
*
*
- * @param token a token
+ * @param token a token (can be null)
*/
public void logout( String token ) {
if( token != null )
diff --git a/core/roboconf-dm-rest-services/metadata.xml b/core/roboconf-dm-rest-services/metadata.xml
index b5470548..93b02a0a 100644
--- a/core/roboconf-dm-rest-services/metadata.xml
+++ b/core/roboconf-dm-rest-services/metadata.xml
@@ -54,6 +54,9 @@
+
+
+
diff --git a/core/roboconf-dm-rest-services/pom.xml b/core/roboconf-dm-rest-services/pom.xml
index d63cb944..e43aff5e 100644
--- a/core/roboconf-dm-rest-services/pom.xml
+++ b/core/roboconf-dm-rest-services/pom.xml
@@ -122,9 +122,9 @@
- org.apache.felix
+ org.osgi
org.osgi.core
- 1.4.0
+ 6.0.0
provided
diff --git a/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/RestApplication.java b/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/RestApplication.java
index 424b8df2..ee5ad725 100644
--- a/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/RestApplication.java
+++ b/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/RestApplication.java
@@ -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;
@@ -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;
/**
@@ -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 );
@@ -100,6 +104,7 @@ public Set getSingletons() {
set.add( this.targetResource );
set.add( this.preferencesResource );
set.add( this.schedulerResource );
+ set.add( this.authenticationResource );
return set;
}
@@ -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
diff --git a/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/ServletRegistrationComponent.java b/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/ServletRegistrationComponent.java
index 7e85fe88..161012a9 100644
--- a/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/ServletRegistrationComponent.java
+++ b/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/ServletRegistrationComponent.java
@@ -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;
@@ -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 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.
@@ -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 );
}
@@ -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 ) {
@@ -130,6 +168,8 @@ public void stopping() throws Exception {
// Reset the application
this.app = null;
this.jerseyServlet = null;
+ this.filterServiceRegistration = null;
+ this.authenticationFilter = null;
}
@@ -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.
diff --git a/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/filters/AuthenticationFilter.java b/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/filters/AuthenticationFilter.java
new file mode 100644
index 00000000..a44a6868
--- /dev/null
+++ b/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/filters/AuthenticationFilter.java
@@ -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.
+ *
+ * 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.
+ *
+ * @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;
+ }
+}
diff --git a/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/resources/IAuthenticationResource.java b/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/resources/IAuthenticationResource.java
new file mode 100644
index 00000000..a8fd4f4f
--- /dev/null
+++ b/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/resources/IAuthenticationResource.java
@@ -0,0 +1,71 @@
+/**
+ * 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.resources;
+
+import javax.ws.rs.CookieParam;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Response;
+
+import net.roboconf.dm.rest.commons.UrlConstants;
+import net.roboconf.dm.rest.services.internal.filters.AuthenticationFilter;
+
+/**
+ * @author Vincent Zurczak - Linagora
+ */
+public interface IAuthenticationResource {
+
+ String PATH = "/" + UrlConstants.AUTHENTICATION;
+ String LOGIN_PATH = PATH + "/e";
+
+
+ /**
+ * Authenticates a user.
+ * @param username a user name
+ * @param password a password
+ * @return a response (the session ID is returned as a cookie)
+ * @see AuthenticationFilter#SESSION_ID for the cookie's name
+ *
+ * @HTTP 200 Login succeeded.
+ * @HTTP 403 Login failed.
+ * @HTTP 500 Invalid server configuration.
+ */
+ @POST
+ @Path( LOGIN_PATH )
+ Response login( @HeaderParam("u") String username, @HeaderParam("p") String password );
+
+
+ /**
+ * Terminates a user session.
+ * @param sessionId a session ID
+ * @return a response
+ * @HTTP 200 Always successful.
+ */
+ @POST
+ @Path("/s")
+ Response logout( @CookieParam( AuthenticationFilter.SESSION_ID ) String sessionId );
+}
diff --git a/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/resources/impl/AuthenticationResource.java b/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/resources/impl/AuthenticationResource.java
new file mode 100644
index 00000000..0c119341
--- /dev/null
+++ b/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/resources/impl/AuthenticationResource.java
@@ -0,0 +1,88 @@
+/**
+ * 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.resources.impl;
+
+import java.util.logging.Logger;
+
+import javax.ws.rs.Path;
+import javax.ws.rs.core.NewCookie;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import net.roboconf.dm.rest.commons.security.AuthenticationManager;
+import net.roboconf.dm.rest.services.internal.filters.AuthenticationFilter;
+import net.roboconf.dm.rest.services.internal.resources.IAuthenticationResource;
+
+/**
+ * @author Vincent Zurczak - Linagora
+ */
+@Path( IAuthenticationResource.PATH )
+public class AuthenticationResource implements IAuthenticationResource {
+
+ private final Logger logger = Logger.getLogger( getClass().getName());
+ private AuthenticationManager authenticationManager;
+
+
+ @Override
+ public Response login( String username, String password ) {
+ this.logger.fine( "Authenticating user " + username + "..." );
+
+ String sessionId;
+ Response response;
+ if( this.authenticationManager == null )
+ response = Response.status( Status.INTERNAL_SERVER_ERROR ).entity( "No authentication manager was available." ).build();
+ else if(( sessionId = this.authenticationManager.login( username, password )) == null )
+ response = Response.status( Status.FORBIDDEN ).entity( "Authentication failed." ).build();
+ else
+ response = Response.ok().cookie( new NewCookie( AuthenticationFilter.SESSION_ID, sessionId )).build();
+
+ // NewCookie's implementation uses NewCookie.DEFAULT_MAX_AGE as the default
+ // validity for a cookie, which means it is valid until the browser is closed.
+ // That's fine for us. In addition, we maintain a validity period on the server, in memory.
+ // This last one is managed by the authentication manager, itself bound to a REALM.
+
+ return response;
+ }
+
+
+ @Override
+ public Response logout( String sessionId ) {
+
+ this.logger.fine( "Terminating session " + sessionId + "..." );
+ if( this.authenticationManager != null )
+ this.authenticationManager.logout( sessionId );
+
+ return Response.ok().build();
+ }
+
+
+ /**
+ * @param authenticationManager the authenticationManager to set
+ */
+ public void setAuthenticationManager( AuthenticationManager authenticationManager ) {
+ this.authenticationManager = authenticationManager;
+ }
+}
diff --git a/core/roboconf-dm-rest-services/src/test/java/net/roboconf/dm/rest/services/internal/ServletRegistrationComponentTest.java b/core/roboconf-dm-rest-services/src/test/java/net/roboconf/dm/rest/services/internal/ServletRegistrationComponentTest.java
index 1efaeddd..b92a282a 100644
--- a/core/roboconf-dm-rest-services/src/test/java/net/roboconf/dm/rest/services/internal/ServletRegistrationComponentTest.java
+++ b/core/roboconf-dm-rest-services/src/test/java/net/roboconf/dm/rest/services/internal/ServletRegistrationComponentTest.java
@@ -31,6 +31,7 @@
import java.util.HashMap;
import java.util.Map;
+import javax.servlet.Filter;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
@@ -44,6 +45,8 @@
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.mockito.Mockito;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
import org.osgi.service.http.HttpContext;
import org.osgi.service.http.HttpService;
import org.osgi.service.http.NamespaceException;
@@ -58,6 +61,8 @@
import net.roboconf.dm.management.Manager;
import net.roboconf.dm.rest.commons.UrlConstants;
import net.roboconf.dm.rest.commons.json.JSonBindingUtils;
+import net.roboconf.dm.rest.commons.security.AuthenticationManager;
+import net.roboconf.dm.rest.services.internal.filters.AuthenticationFilter;
import net.roboconf.messaging.api.MessagingConstants;
/**
@@ -72,8 +77,12 @@ public class ServletRegistrationComponentTest {
private TestManagerWrapper managerWrapper;
private TestApplication app;
+ private ServletRegistrationComponent register;
+ private BundleContext bundleContext;
+
@Before
+ @SuppressWarnings( "unchecked" )
public void initializeManager() throws Exception {
this.manager = new Manager();
this.manager.setMessagingType(MessagingConstants.FACTORY_TEST);
@@ -85,6 +94,14 @@ public void initializeManager() throws Exception {
this.managerWrapper = new TestManagerWrapper( this.manager );
this.managerWrapper.addManagedApplication( new ManagedApplication( this.app ));
+
+ this.bundleContext = Mockito.mock( BundleContext.class );
+ Mockito.when( this.bundleContext.registerService(
+ Mockito.eq( Filter.class ),
+ Mockito.any( Filter.class ),
+ Mockito.any( Dictionary.class ))).thenReturn( Mockito.mock( ServiceRegistration.class ));
+
+ this.register = new ServletRegistrationComponent( this.bundleContext );
}
@@ -97,27 +114,35 @@ public void stopManager() {
@Test
public void testStop_httpServiceIsNull() throws Exception {
- ServletRegistrationComponent register = new ServletRegistrationComponent();
- register.stopping();
+ this.register.stopping();
+ Mockito.verifyZeroInteractions( this.bundleContext );
}
@Test
+ @SuppressWarnings( "unchecked" )
public void testStartAndStop() throws Exception {
- ServletRegistrationComponent register = new ServletRegistrationComponent();
-
// No error if these methods are called before the component is started.
- register.schedulerAppears();
- register.schedulerDisappears();
+ this.register.schedulerAppears();
+ this.register.schedulerDisappears();
+
+ this.register.mavenResolverAppears();
+ this.register.mavenResolverDisappears();
// Deal with the HTTP service.
HttpServiceForTest httpService = new HttpServiceForTest();
- register.setHttpService( httpService );
+ this.register.setHttpService( httpService );
Assert.assertEquals( 0, httpService.pathToServlet.size());
- register.starting();
+ Mockito.verifyZeroInteractions( this.bundleContext );
+ this.register.starting();
+
Assert.assertEquals( 3, httpService.pathToServlet.size());
+ Mockito.verify( this.bundleContext, Mockito.only()).registerService(
+ Mockito.eq( Filter.class ),
+ Mockito.any( AuthenticationFilter.class ),
+ Mockito.any( Dictionary.class ));
ServletContainer jerseyServlet = (ServletContainer) httpService.pathToServlet.get( ServletRegistrationComponent.REST_CONTEXT );
Assert.assertNotNull( jerseyServlet );
@@ -128,17 +153,28 @@ public void testStartAndStop() throws Exception {
HttpServlet websocketServlet = (HttpServlet) httpService.pathToServlet.get( ServletRegistrationComponent.WEBSOCKET_CONTEXT );
Assert.assertNotNull( websocketServlet );
+ // Check there is no authentication manager
+ Assert.assertNull( this.register.authenticationMngr );
+ this.register.setAuthenticationRealm( "realm" );
+ Assert.assertNotNull( this.register.authenticationMngr );
+
// Update the scheduler...
- register.schedulerAppears();
- register.schedulerDisappears();
+ this.register.schedulerAppears();
+ this.register.schedulerDisappears();
// Update the URL resolver
- register.mavenResolverAppears();
- register.mavenResolverDisappears();
+ this.register.mavenResolverAppears();
+ this.register.mavenResolverDisappears();
// Stop...
- register.stopping();
+ this.register.stopping();
+
Assert.assertEquals( 0, httpService.pathToServlet.size());
+ Assert.assertNull( this.register.app );
+ Assert.assertNull( this.register.authenticationFilter );
+ Assert.assertNull( this.register.jerseyServlet );
+ Assert.assertNull( this.register.filterServiceRegistration );
+ Assert.assertNotNull( this.register.authenticationMngr );
}
@@ -205,31 +241,93 @@ public void testJsonSerialization_instance() throws Exception {
public void testSetEnableCors() throws Exception {
// No NPE
- ServletRegistrationComponent register = new ServletRegistrationComponent();
- register.setEnableCors( true );
- register.setEnableCors( false );
+ this.register.setEnableCors( true );
+ this.register.setEnableCors( false );
+
+ // Act like if the component had been started
+ this.register.app = Mockito.spy( new RestApplication( this.manager ));
+ this.register.jerseyServlet = Mockito.mock( ServletContainer.class );
+
+ this.register.setEnableCors( true );
+ Mockito.verify( this.register.app, Mockito.times( 1 )).enableCors( true );
+ Mockito.verify( this.register.jerseyServlet, Mockito.only()).reload();
+
+ Mockito.reset( this.register.app );
+ Mockito.reset( this.register.jerseyServlet );
+
+ this.register.setEnableCors( false );
+ Mockito.verify( this.register.app, Mockito.times( 1 )).enableCors( false );
+ Mockito.verify( this.register.jerseyServlet, Mockito.only()).reload();
+
+ // Stop...
+ this.register.stopping();
+
+ // No NPE
+ this.register.setEnableCors( true );
+ this.register.setEnableCors( false );
+ }
+
+
+ @Test
+ public void testSetSessionPeriod() throws Exception {
+
+ // No NPE
+ this.register.setSessionPeriod( 50 );
// Act like if the component had been started
- register.app = Mockito.spy( new RestApplication( this.manager ));
- register.jerseyServlet = Mockito.mock( ServletContainer.class );
+ this.register.authenticationFilter = Mockito.mock( AuthenticationFilter.class );
- register.setEnableCors( true );
- Mockito.verify( register.app, Mockito.times( 1 )).enableCors( true );
- Mockito.verify( register.jerseyServlet, Mockito.only()).reload();
+ this.register.setSessionPeriod( 500 );
+ Mockito.verify( this.register.authenticationFilter, Mockito.only()).setSessionPeriod( 500 );
- Mockito.reset( register.app );
- Mockito.reset( register.jerseyServlet );
+ // Stop...
+ this.register.stopping();
+
+ // No NPE
+ this.register.setSessionPeriod( -1 );
+ }
+
+
+ @Test
+ public void testSetEnableAuthentication() throws Exception {
+
+ // No NPE
+ this.register.setEnableAuthentication( true );
+ this.register.setEnableAuthentication( false );
+
+ // Act like if the component had been started
+ this.register.authenticationFilter = Mockito.mock( AuthenticationFilter.class );
+
+ this.register.setEnableAuthentication( true );
+ Mockito.verify( this.register.authenticationFilter, Mockito.only()).setAuthenticationEnabled( true );
+
+ // Stop...
+ this.register.stopping();
+
+ // No NPE
+ this.register.setEnableAuthentication( false );
+ }
+
+
+ @Test
+ public void testSetAuthenticationRealm() throws Exception {
+
+ // No NPE
+ Assert.assertNull( this.register.authenticationMngr );
+ this.register.setAuthenticationRealm( "realm" );
+ Assert.assertNotNull( this.register.authenticationMngr );
+
+ // Act like if the component had been started
+ this.register.authenticationFilter = Mockito.mock( AuthenticationFilter.class );
- register.setEnableCors( false );
- Mockito.verify( register.app, Mockito.times( 1 )).enableCors( false );
- Mockito.verify( register.jerseyServlet, Mockito.only()).reload();
+ this.register.setAuthenticationRealm( "realm2" );
+ Mockito.verify( this.register.authenticationFilter, Mockito.only()).setAuthenticationMngr( Mockito.any( AuthenticationManager.class ));
// Stop...
- register.stopping();
+ this.register.stopping();
// No NPE
- register.setEnableCors( true );
- register.setEnableCors( false );
+ this.register.setAuthenticationRealm( "realm" );
}
diff --git a/core/roboconf-dm-rest-services/src/test/java/net/roboconf/dm/rest/services/internal/filters/AuthenticationFilterTest.java b/core/roboconf-dm-rest-services/src/test/java/net/roboconf/dm/rest/services/internal/filters/AuthenticationFilterTest.java
new file mode 100644
index 00000000..cd80d5aa
--- /dev/null
+++ b/core/roboconf-dm-rest-services/src/test/java/net/roboconf/dm/rest/services/internal/filters/AuthenticationFilterTest.java
@@ -0,0 +1,179 @@
+/**
+ * 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 javax.servlet.FilterChain;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import net.roboconf.dm.rest.commons.security.AuthenticationManager;
+import net.roboconf.dm.rest.services.internal.resources.IAuthenticationResource;
+
+/**
+ * @author Vincent Zurczak - Linagora
+ */
+public class AuthenticationFilterTest {
+
+ @Test
+ public void forCodeCoverage() throws Exception {
+
+ AuthenticationFilter filter = new AuthenticationFilter();
+ filter.init( null );
+ filter.destroy();
+ }
+
+
+ @Test
+ public void testDoFiler_noAuthentication() throws Exception {
+
+ AuthenticationFilter filter = new AuthenticationFilter();
+ filter.setAuthenticationEnabled( false );
+
+ ServletRequest req = Mockito.mock( ServletRequest.class );
+ ServletResponse resp = Mockito.mock( ServletResponse.class );
+ FilterChain chain = Mockito.mock( FilterChain.class );
+
+ filter.doFilter( req, resp, chain );
+ Mockito.verifyZeroInteractions( req );
+ Mockito.verifyZeroInteractions( resp );
+ Mockito.verify( chain, Mockito.only()).doFilter( req, resp );
+ }
+
+
+ @Test
+ public void testDoFiler_withAuthentication_noCookie() throws Exception {
+
+ AuthenticationFilter filter = new AuthenticationFilter();
+ filter.setAuthenticationEnabled( true );
+
+ HttpServletRequest req = Mockito.mock( HttpServletRequest.class );
+ HttpServletResponse resp = Mockito.mock( HttpServletResponse.class );
+ FilterChain chain = Mockito.mock( FilterChain.class );
+
+ filter.doFilter( req, resp, chain );
+ Mockito.verify( req ).getCookies();
+ Mockito.verify( req ).getRequestURI();
+ Mockito.verifyNoMoreInteractions( req );
+
+ Mockito.verify( resp, Mockito.only()).sendError( 403, "Authentication is required." );
+ Mockito.verifyZeroInteractions( chain );
+ }
+
+
+ @Test
+ public void testDoFiler_withAuthentication_withCookie_loggedIn() throws Exception {
+
+ final String sessionId = "a1a2a3a4";
+ final long sessionPeriod = -1;
+
+ AuthenticationFilter filter = new AuthenticationFilter();
+ filter.setAuthenticationEnabled( true );
+ filter.setSessionPeriod( sessionPeriod );
+
+ AuthenticationManager authMngr = Mockito.mock( AuthenticationManager.class );
+ Mockito.when( authMngr.isSessionValid( sessionId, sessionPeriod )).thenReturn( true );
+ filter.setAuthenticationMngr( authMngr );
+
+ FilterChain chain = Mockito.mock( FilterChain.class );
+ HttpServletResponse resp = Mockito.mock( HttpServletResponse.class );
+ HttpServletRequest req = Mockito.mock( HttpServletRequest.class );
+ Mockito.when( req.getCookies()).thenReturn( new Cookie[] {
+ new Cookie( "as", "as" ),
+ new Cookie( AuthenticationFilter.SESSION_ID, sessionId )
+ });
+
+ filter.doFilter( req, resp, chain );
+ Mockito.verify( req ).getCookies();
+ Mockito.verify( req ).getRequestURI();
+ Mockito.verifyNoMoreInteractions( req );
+
+ Mockito.verify( authMngr ).isSessionValid( sessionId, sessionPeriod );
+ Mockito.verify( chain, Mockito.only()).doFilter( req, resp );
+ Mockito.verifyZeroInteractions( resp );
+ }
+
+
+ @Test
+ public void testDoFiler_withAuthentication_withCookie_notLoggedIn() throws Exception {
+
+ final String sessionId = "a1a2a3a4";
+ final long sessionPeriod = -1;
+
+ AuthenticationFilter filter = new AuthenticationFilter();
+ filter.setAuthenticationEnabled( true );
+ filter.setSessionPeriod( sessionPeriod );
+
+ AuthenticationManager authMngr = Mockito.mock( AuthenticationManager.class );
+ Mockito.when( authMngr.isSessionValid( sessionId, sessionPeriod )).thenReturn( false );
+ filter.setAuthenticationMngr( authMngr );
+
+ FilterChain chain = Mockito.mock( FilterChain.class );
+ HttpServletResponse resp = Mockito.mock( HttpServletResponse.class );
+ HttpServletRequest req = Mockito.mock( HttpServletRequest.class );
+ Mockito.when( req.getCookies()).thenReturn( new Cookie[] {
+ new Cookie( "as", "as" ),
+ new Cookie( AuthenticationFilter.SESSION_ID, sessionId )
+ });
+
+ filter.doFilter( req, resp, chain );
+ Mockito.verify( req ).getCookies();
+ Mockito.verify( req ).getRequestURI();
+ Mockito.verifyNoMoreInteractions( req );
+
+ Mockito.verify( authMngr ).isSessionValid( sessionId, sessionPeriod );
+ Mockito.verifyZeroInteractions( chain );
+ Mockito.verify( resp, Mockito.only()).sendError( 403, "Authentication is required." );
+ }
+
+
+ @Test
+ public void testDoFiler_withAuthentication_noCookie_butLoginPageRequested() throws Exception {
+
+ AuthenticationFilter filter = new AuthenticationFilter();
+ filter.setAuthenticationEnabled( true );
+
+ HttpServletRequest req = Mockito.mock( HttpServletRequest.class );
+ Mockito.when( req.getRequestURI()).thenReturn( IAuthenticationResource.LOGIN_PATH );
+ Mockito.when( req.getCookies()).thenReturn( new Cookie[ 0 ]);
+
+ HttpServletResponse resp = Mockito.mock( HttpServletResponse.class );
+ FilterChain chain = Mockito.mock( FilterChain.class );
+
+ filter.doFilter( req, resp, chain );
+ Mockito.verify( req ).getCookies();
+ Mockito.verify( req ).getRequestURI();
+ Mockito.verifyNoMoreInteractions( req );
+
+ Mockito.verify( chain, Mockito.only()).doFilter( req, resp );
+ Mockito.verifyZeroInteractions( resp );
+ }
+}
diff --git a/core/roboconf-dm-rest-services/src/test/java/net/roboconf/dm/rest/services/internal/resources/impl/AuthenticationResourceTest.java b/core/roboconf-dm-rest-services/src/test/java/net/roboconf/dm/rest/services/internal/resources/impl/AuthenticationResourceTest.java
new file mode 100644
index 00000000..a7ddc279
--- /dev/null
+++ b/core/roboconf-dm-rest-services/src/test/java/net/roboconf/dm/rest/services/internal/resources/impl/AuthenticationResourceTest.java
@@ -0,0 +1,84 @@
+/**
+ * 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.resources.impl;
+
+import javax.security.auth.login.LoginException;
+import javax.ws.rs.core.NewCookie;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import net.roboconf.dm.rest.commons.security.AuthenticationManager;
+import net.roboconf.dm.rest.commons.security.AuthenticationManager.IAuthService;
+import net.roboconf.dm.rest.services.internal.filters.AuthenticationFilter;
+
+/**
+ * @author Vincent Zurczak - Linagora
+ */
+public class AuthenticationResourceTest {
+
+ @Test
+ public void testLoginAndLogout() throws Exception {
+
+ // No authentication manager
+ AuthenticationResource res = new AuthenticationResource();
+ Response resp = res.login( "kikou", "pwd" );
+ Assert.assertEquals( Status.INTERNAL_SERVER_ERROR.getStatusCode(), resp.getStatus());
+
+ resp = res.logout( null );
+ Assert.assertEquals( Status.OK.getStatusCode(), resp.getStatus());
+
+ // Set one
+ AuthenticationManager authMngr = new AuthenticationManager( "my realm" );
+ IAuthService authService = Mockito.mock( IAuthService.class );
+ authMngr.setAuthService( authService );
+ res.setAuthenticationManager( authMngr );
+
+ // Authentication will work for ANY user, except for "u1"
+ Mockito.doThrow( new LoginException( "for test" )).when( authService ).authenticate( "u1", "p1" );
+
+ resp = res.login( "u2", "p2" );
+ Assert.assertEquals( Status.OK.getStatusCode(), resp.getStatus());
+
+ NewCookie cookie = (NewCookie) resp.getMetadata().getFirst( "Set-Cookie" );
+ Assert.assertNotNull( cookie );
+ Assert.assertEquals( AuthenticationFilter.SESSION_ID, cookie.getName());
+ Assert.assertNotNull( cookie.getValue());
+ Assert.assertTrue( authMngr.isSessionValid( cookie.getValue(), -1 ));
+
+ // Log out
+ res.logout( cookie.getValue());
+ Assert.assertFalse( authMngr.isSessionValid( cookie.getValue(), -1 ));
+
+ // Verify "u1" cannot login
+ resp = res.login( "u1", "p1" );
+ Assert.assertEquals( Status.FORBIDDEN.getStatusCode(), resp.getStatus());
+ Assert.assertEquals( 0, resp.getMetadata().size());
+ }
+}
diff --git a/core/roboconf-dm-rest-services/src/test/java/net/roboconf/dm/rest/services/internal/resources/impl/ManagementResourceTest.java b/core/roboconf-dm-rest-services/src/test/java/net/roboconf/dm/rest/services/internal/resources/impl/ManagementResourceTest.java
index dabd2d7a..f0070e56 100644
--- a/core/roboconf-dm-rest-services/src/test/java/net/roboconf/dm/rest/services/internal/resources/impl/ManagementResourceTest.java
+++ b/core/roboconf-dm-rest-services/src/test/java/net/roboconf/dm/rest/services/internal/resources/impl/ManagementResourceTest.java
@@ -30,6 +30,9 @@
import java.io.InputStream;
import java.util.List;
import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.LogManager;
+import java.util.logging.Logger;
import javax.ws.rs.core.Response.Status;
@@ -153,6 +156,11 @@ public void testListApplicationTemplates() throws Exception {
TestApplicationTemplate tpl = new TestApplicationTemplate();
this.managerWrapper.getApplicationTemplates().put( tpl, Boolean.TRUE );
+ // Improve code coverage by setting the log level to finest
+ Logger logger = Logger.getLogger( ManagementResource.class.getName());
+ LogManager.getLogManager().addLogger( logger );
+ logger.setLevel( Level.FINEST );
+
// Get ALL the templates
templates = this.resource.listApplicationTemplates();
Assert.assertNotNull( templates );
diff --git a/karaf/roboconf-karaf-dist-dm/src/main/resources/etc/net.roboconf.dm.rest.services.configuration.cfg b/karaf/roboconf-karaf-dist-dm/src/main/resources/etc/net.roboconf.dm.rest.services.configuration.cfg
index af436ef3..f9d5bc8f 100644
--- a/karaf/roboconf-karaf-dist-dm/src/main/resources/etc/net.roboconf.dm.rest.services.configuration.cfg
+++ b/karaf/roboconf-karaf-dist-dm/src/main/resources/etc/net.roboconf.dm.rest.services.configuration.cfg
@@ -19,3 +19,15 @@
# CORS = https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
# It should be disabled in production environments.
enable-cors = false
+
+# Enable or disable authentication.
+# Users are authenticated against the specified REALM.
+enable-authentication = false
+
+# The REALM that is used for authentication.
+# We use Karaf realms.
+authentication-realm = karaf
+
+# The period of validity for a session once a user is logged in.
+# Expressed in seconds. Use a negative value for infinite validity.
+session-period = -1
diff --git a/karaf/roboconf-karaf-dist-dm/src/main/resources/etc/org.ops4j.pax.logging.cfg b/karaf/roboconf-karaf-dist-dm/src/main/resources/etc/org.ops4j.pax.logging.cfg
index e38c43e1..3fdd6cee 100644
--- a/karaf/roboconf-karaf-dist-dm/src/main/resources/etc/org.ops4j.pax.logging.cfg
+++ b/karaf/roboconf-karaf-dist-dm/src/main/resources/etc/org.ops4j.pax.logging.cfg
@@ -70,3 +70,4 @@ log4j.logger.net.roboconf.dm.internal.tasks.CheckerForHeartbeatsTask=DEBUG, robo
log4j.logger.net.roboconf.dm.rest.services.internal.resources.impl.ApplicationResource=DEBUG, roboconf
log4j.logger.net.roboconf.target.api.AbstractThreadedTargetHandler$CheckingRunnable=DEBUG, roboconf
log4j.logger.net.roboconf.dm.internal.environment.messaging.DmMessageProcessor=DEBUG, roboconf
+log4j.org.apache.karaf.jaas.modules.audit.LogAuditLoginModule=WARN, karaf
From 179b79be0162481773754382c2c6aebfcdebe28c Mon Sep 17 00:00:00 2001
From: Vincent Zurczak
Date: Thu, 6 Apr 2017 20:22:44 +0200
Subject: [PATCH 3/3] #632 User authentication (WTF)
---
.../dm/rest/commons/UrlConstants.java | 2 +
.../security/AuthenticationManager.java | 8 +
.../ServletRegistrationComponent.java | 12 +-
.../filters/AuthenticationFilter.java | 8 +-
.../resources/IAuthenticationResource.java | 4 +-
.../impl/AuthenticationResource.java | 16 +-
.../ServletRegistrationComponentTest.java | 2 +-
.../filters/AuthenticationFilterTest.java | 14 +-
.../impl/AuthenticationResourceTest.java | 4 +-
.../resources/etc/org.ops4j.pax.logging.cfg | 4 +-
miscellaneous/roboconf-dm-rest-client/pom.xml | 6 +
.../net/roboconf/dm/rest/client/WsClient.java | 57 ++++++-
.../delegates/ApplicationWsDelegate.java | 69 +++++---
.../delegates/AuthenticationWsDelegate.java | 118 ++++++++++++++
.../client/delegates/DebugWsDelegate.java | 29 ++--
.../delegates/ManagementWsDelegate.java | 47 +++---
.../delegates/PreferencesWsDelegate.java | 8 +-
.../client/delegates/SchedulerWsDelegate.java | 28 +++-
.../client/delegates/TargetWsDelegate.java | 21 ++-
.../AuthenticationWsDelegateTest.java | 105 ++++++++++++
.../delegates/ManagementWsDelegateTest.java | 6 +
.../in/memory/RestSecuredServicesTest.java | 152 ++++++++++++++++++
22 files changed, 627 insertions(+), 93 deletions(-)
create mode 100644 miscellaneous/roboconf-dm-rest-client/src/main/java/net/roboconf/dm/rest/client/delegates/AuthenticationWsDelegate.java
create mode 100644 miscellaneous/roboconf-dm-rest-client/src/test/java/net/roboconf/dm/rest/client/delegates/AuthenticationWsDelegateTest.java
create mode 100644 miscellaneous/roboconf-integration-tests-dm-with-agents-in-memory/src/test/java/net/roboconf/integration/tests/dm/with/agents/in/memory/RestSecuredServicesTest.java
diff --git a/core/roboconf-dm-rest-commons/src/main/java/net/roboconf/dm/rest/commons/UrlConstants.java b/core/roboconf-dm-rest-commons/src/main/java/net/roboconf/dm/rest/commons/UrlConstants.java
index de3549d0..eff86750 100644
--- a/core/roboconf-dm-rest-commons/src/main/java/net/roboconf/dm/rest/commons/UrlConstants.java
+++ b/core/roboconf-dm-rest-commons/src/main/java/net/roboconf/dm/rest/commons/UrlConstants.java
@@ -37,4 +37,6 @@ public interface UrlConstants {
String PREFERENCES = "preferences";
String SCHEDULER = "scheduler";
String AUTHENTICATION = "auth";
+
+ String SESSION_ID = "sid";
}
diff --git a/core/roboconf-dm-rest-commons/src/main/java/net/roboconf/dm/rest/commons/security/AuthenticationManager.java b/core/roboconf-dm-rest-commons/src/main/java/net/roboconf/dm/rest/commons/security/AuthenticationManager.java
index d257bc92..7c4e9d57 100644
--- a/core/roboconf-dm-rest-commons/src/main/java/net/roboconf/dm/rest/commons/security/AuthenticationManager.java
+++ b/core/roboconf-dm-rest-commons/src/main/java/net/roboconf/dm/rest/commons/security/AuthenticationManager.java
@@ -72,6 +72,14 @@ public class AuthenticationManager {
private IAuthService authService;
+ /**
+ * @author Vincent Zurczak - Linagora
+ */
+ public static enum REST_ROLES {
+ ADM, TPL_RW, APP_RW, APP_R
+ }
+
+
/**
* Constructor.
diff --git a/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/ServletRegistrationComponent.java b/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/ServletRegistrationComponent.java
index 161012a9..3ec8389c 100644
--- a/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/ServletRegistrationComponent.java
+++ b/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/ServletRegistrationComponent.java
@@ -111,6 +111,7 @@ public void starting() throws Exception {
this.app.setScheduler( this.scheduler );
this.app.setMavenResolver( this.mavenResolver );
this.app.enableCors( this.enableCors );
+ this.app.setAuthenticationManager( this.authenticationMngr );
Dictionary initParams = new Hashtable<> ();
initParams.put( "servlet-name", "Roboconf DM (REST)" );
@@ -135,12 +136,17 @@ public void starting() throws Exception {
// Register a filter for authentication
this.authenticationFilter = new AuthenticationFilter();
this.authenticationFilter.setAuthenticationEnabled( this.enableAuthentication );
- this.authenticationFilter.setAuthenticationMngr( this.authenticationMngr );
+ this.authenticationFilter.setAuthenticationManager( this.authenticationMngr );
this.authenticationFilter.setSessionPeriod( this.sessionPeriod );
initParams = new Hashtable<> ();
initParams.put( "urlPatterns", "*" );
- this.filterServiceRegistration = this.bundleContext.registerService( Filter.class, this.authenticationFilter, initParams );
+
+ // Consider the bundle context can be null (e.g. when used outside of OSGi)
+ if( this.bundleContext != null )
+ this.filterServiceRegistration = this.bundleContext.registerService( Filter.class, this.authenticationFilter, initParams );
+ else
+ this.logger.warning( "No bundle context was available, the authentication filter was not registered." );
}
@@ -289,7 +295,7 @@ public void setAuthenticationRealm( String authenticationRealm ) {
// Propagate the change
if( this.authenticationFilter != null )
- this.authenticationFilter.setAuthenticationMngr( this.authenticationMngr );
+ this.authenticationFilter.setAuthenticationManager( this.authenticationMngr );
if( this.app != null )
this.app.setAuthenticationManager( this.authenticationMngr );
diff --git a/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/filters/AuthenticationFilter.java b/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/filters/AuthenticationFilter.java
index a44a6868..37fd8af8 100644
--- a/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/filters/AuthenticationFilter.java
+++ b/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/filters/AuthenticationFilter.java
@@ -39,6 +39,7 @@
import javax.servlet.http.HttpServletResponse;
import net.roboconf.core.utils.Utils;
+import net.roboconf.dm.rest.commons.UrlConstants;
import net.roboconf.dm.rest.commons.security.AuthenticationManager;
import net.roboconf.dm.rest.services.internal.resources.IAuthenticationResource;
@@ -55,7 +56,6 @@
*/
public class AuthenticationFilter implements Filter {
- public static final String SESSION_ID = "sid";
private final Logger logger = Logger.getLogger( getClass().getName());
private AuthenticationManager authenticationMngr;
@@ -81,7 +81,7 @@ public void doFilter( ServletRequest req, ServletResponse resp, FilterChain chai
Cookie[] cookies = request.getCookies();
if( cookies != null ) {
for( Cookie cookie : cookies ) {
- if( SESSION_ID.equals( cookie.getName())) {
+ if( UrlConstants.SESSION_ID.equals( cookie.getName())) {
sessionId = cookie.getValue();
break;
}
@@ -99,7 +99,7 @@ public void doFilter( ServletRequest req, ServletResponse resp, FilterChain chai
// 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 );
+ boolean loginRequest = requestedPath.endsWith( IAuthenticationResource.PATH + IAuthenticationResource.LOGIN_PATH );
if( loggedIn || loginRequest ) {
chain.doFilter( request, response );
} else {
@@ -132,7 +132,7 @@ public void setAuthenticationEnabled( boolean authenticationEnabled ) {
/**
* @param authenticationMngr the authenticationMngr to set
*/
- public void setAuthenticationMngr( AuthenticationManager authenticationMngr ) {
+ public void setAuthenticationManager( AuthenticationManager authenticationMngr ) {
this.authenticationMngr = authenticationMngr;
}
diff --git a/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/resources/IAuthenticationResource.java b/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/resources/IAuthenticationResource.java
index a8fd4f4f..51407abb 100644
--- a/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/resources/IAuthenticationResource.java
+++ b/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/resources/IAuthenticationResource.java
@@ -40,7 +40,7 @@
public interface IAuthenticationResource {
String PATH = "/" + UrlConstants.AUTHENTICATION;
- String LOGIN_PATH = PATH + "/e";
+ String LOGIN_PATH = "/e";
/**
@@ -67,5 +67,5 @@ public interface IAuthenticationResource {
*/
@POST
@Path("/s")
- Response logout( @CookieParam( AuthenticationFilter.SESSION_ID ) String sessionId );
+ Response logout( @CookieParam( UrlConstants.SESSION_ID ) String sessionId );
}
diff --git a/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/resources/impl/AuthenticationResource.java b/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/resources/impl/AuthenticationResource.java
index 0c119341..19d8681a 100644
--- a/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/resources/impl/AuthenticationResource.java
+++ b/core/roboconf-dm-rest-services/src/main/java/net/roboconf/dm/rest/services/internal/resources/impl/AuthenticationResource.java
@@ -32,8 +32,8 @@
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
+import net.roboconf.dm.rest.commons.UrlConstants;
import net.roboconf.dm.rest.commons.security.AuthenticationManager;
-import net.roboconf.dm.rest.services.internal.filters.AuthenticationFilter;
import net.roboconf.dm.rest.services.internal.resources.IAuthenticationResource;
/**
@@ -52,12 +52,18 @@ public Response login( String username, String password ) {
String sessionId;
Response response;
- if( this.authenticationManager == null )
+ if( this.authenticationManager == null ) {
response = Response.status( Status.INTERNAL_SERVER_ERROR ).entity( "No authentication manager was available." ).build();
- else if(( sessionId = this.authenticationManager.login( username, password )) == null )
+ this.logger.fine( "No authentication manager was available. User was " + username );
+
+ } else if(( sessionId = this.authenticationManager.login( username, password )) == null ) {
response = Response.status( Status.FORBIDDEN ).entity( "Authentication failed." ).build();
- else
- response = Response.ok().cookie( new NewCookie( AuthenticationFilter.SESSION_ID, sessionId )).build();
+ this.logger.fine( "Authentication failed. User was " + username );
+
+ } else {
+ response = Response.ok().cookie( new NewCookie( UrlConstants.SESSION_ID, sessionId )).build();
+ this.logger.fine( "Authentication succeeded. User was " + username );
+ }
// NewCookie's implementation uses NewCookie.DEFAULT_MAX_AGE as the default
// validity for a cookie, which means it is valid until the browser is closed.
diff --git a/core/roboconf-dm-rest-services/src/test/java/net/roboconf/dm/rest/services/internal/ServletRegistrationComponentTest.java b/core/roboconf-dm-rest-services/src/test/java/net/roboconf/dm/rest/services/internal/ServletRegistrationComponentTest.java
index b92a282a..2840eb5a 100644
--- a/core/roboconf-dm-rest-services/src/test/java/net/roboconf/dm/rest/services/internal/ServletRegistrationComponentTest.java
+++ b/core/roboconf-dm-rest-services/src/test/java/net/roboconf/dm/rest/services/internal/ServletRegistrationComponentTest.java
@@ -321,7 +321,7 @@ public void testSetAuthenticationRealm() throws Exception {
this.register.authenticationFilter = Mockito.mock( AuthenticationFilter.class );
this.register.setAuthenticationRealm( "realm2" );
- Mockito.verify( this.register.authenticationFilter, Mockito.only()).setAuthenticationMngr( Mockito.any( AuthenticationManager.class ));
+ Mockito.verify( this.register.authenticationFilter, Mockito.only()).setAuthenticationManager( Mockito.any( AuthenticationManager.class ));
// Stop...
this.register.stopping();
diff --git a/core/roboconf-dm-rest-services/src/test/java/net/roboconf/dm/rest/services/internal/filters/AuthenticationFilterTest.java b/core/roboconf-dm-rest-services/src/test/java/net/roboconf/dm/rest/services/internal/filters/AuthenticationFilterTest.java
index cd80d5aa..9a5a8ff0 100644
--- a/core/roboconf-dm-rest-services/src/test/java/net/roboconf/dm/rest/services/internal/filters/AuthenticationFilterTest.java
+++ b/core/roboconf-dm-rest-services/src/test/java/net/roboconf/dm/rest/services/internal/filters/AuthenticationFilterTest.java
@@ -35,6 +35,7 @@
import org.junit.Test;
import org.mockito.Mockito;
+import net.roboconf.dm.rest.commons.UrlConstants;
import net.roboconf.dm.rest.commons.security.AuthenticationManager;
import net.roboconf.dm.rest.services.internal.resources.IAuthenticationResource;
@@ -76,6 +77,7 @@ public void testDoFiler_withAuthentication_noCookie() throws Exception {
filter.setAuthenticationEnabled( true );
HttpServletRequest req = Mockito.mock( HttpServletRequest.class );
+ Mockito.when( req.getRequestURI()).thenReturn( "/whatever" );
HttpServletResponse resp = Mockito.mock( HttpServletResponse.class );
FilterChain chain = Mockito.mock( FilterChain.class );
@@ -101,14 +103,15 @@ public void testDoFiler_withAuthentication_withCookie_loggedIn() throws Exceptio
AuthenticationManager authMngr = Mockito.mock( AuthenticationManager.class );
Mockito.when( authMngr.isSessionValid( sessionId, sessionPeriod )).thenReturn( true );
- filter.setAuthenticationMngr( authMngr );
+ filter.setAuthenticationManager( authMngr );
FilterChain chain = Mockito.mock( FilterChain.class );
HttpServletResponse resp = Mockito.mock( HttpServletResponse.class );
HttpServletRequest req = Mockito.mock( HttpServletRequest.class );
+ Mockito.when( req.getRequestURI()).thenReturn( "/whatever" );
Mockito.when( req.getCookies()).thenReturn( new Cookie[] {
new Cookie( "as", "as" ),
- new Cookie( AuthenticationFilter.SESSION_ID, sessionId )
+ new Cookie( UrlConstants.SESSION_ID, sessionId )
});
filter.doFilter( req, resp, chain );
@@ -134,14 +137,15 @@ public void testDoFiler_withAuthentication_withCookie_notLoggedIn() throws Excep
AuthenticationManager authMngr = Mockito.mock( AuthenticationManager.class );
Mockito.when( authMngr.isSessionValid( sessionId, sessionPeriod )).thenReturn( false );
- filter.setAuthenticationMngr( authMngr );
+ filter.setAuthenticationManager( authMngr );
FilterChain chain = Mockito.mock( FilterChain.class );
HttpServletResponse resp = Mockito.mock( HttpServletResponse.class );
HttpServletRequest req = Mockito.mock( HttpServletRequest.class );
+ Mockito.when( req.getRequestURI()).thenReturn( "/whatever" );
Mockito.when( req.getCookies()).thenReturn( new Cookie[] {
new Cookie( "as", "as" ),
- new Cookie( AuthenticationFilter.SESSION_ID, sessionId )
+ new Cookie( UrlConstants.SESSION_ID, sessionId )
});
filter.doFilter( req, resp, chain );
@@ -162,7 +166,7 @@ public void testDoFiler_withAuthentication_noCookie_butLoginPageRequested() thro
filter.setAuthenticationEnabled( true );
HttpServletRequest req = Mockito.mock( HttpServletRequest.class );
- Mockito.when( req.getRequestURI()).thenReturn( IAuthenticationResource.LOGIN_PATH );
+ Mockito.when( req.getRequestURI()).thenReturn( IAuthenticationResource.PATH + IAuthenticationResource.LOGIN_PATH );
Mockito.when( req.getCookies()).thenReturn( new Cookie[ 0 ]);
HttpServletResponse resp = Mockito.mock( HttpServletResponse.class );
diff --git a/core/roboconf-dm-rest-services/src/test/java/net/roboconf/dm/rest/services/internal/resources/impl/AuthenticationResourceTest.java b/core/roboconf-dm-rest-services/src/test/java/net/roboconf/dm/rest/services/internal/resources/impl/AuthenticationResourceTest.java
index a7ddc279..524ec069 100644
--- a/core/roboconf-dm-rest-services/src/test/java/net/roboconf/dm/rest/services/internal/resources/impl/AuthenticationResourceTest.java
+++ b/core/roboconf-dm-rest-services/src/test/java/net/roboconf/dm/rest/services/internal/resources/impl/AuthenticationResourceTest.java
@@ -34,9 +34,9 @@
import org.junit.Test;
import org.mockito.Mockito;
+import net.roboconf.dm.rest.commons.UrlConstants;
import net.roboconf.dm.rest.commons.security.AuthenticationManager;
import net.roboconf.dm.rest.commons.security.AuthenticationManager.IAuthService;
-import net.roboconf.dm.rest.services.internal.filters.AuthenticationFilter;
/**
* @author Vincent Zurczak - Linagora
@@ -68,7 +68,7 @@ public void testLoginAndLogout() throws Exception {
NewCookie cookie = (NewCookie) resp.getMetadata().getFirst( "Set-Cookie" );
Assert.assertNotNull( cookie );
- Assert.assertEquals( AuthenticationFilter.SESSION_ID, cookie.getName());
+ Assert.assertEquals( UrlConstants.SESSION_ID, cookie.getName());
Assert.assertNotNull( cookie.getValue());
Assert.assertTrue( authMngr.isSessionValid( cookie.getValue(), -1 ));
diff --git a/karaf/roboconf-karaf-dist-dm/src/main/resources/etc/org.ops4j.pax.logging.cfg b/karaf/roboconf-karaf-dist-dm/src/main/resources/etc/org.ops4j.pax.logging.cfg
index 3fdd6cee..fc1edfcf 100644
--- a/karaf/roboconf-karaf-dist-dm/src/main/resources/etc/org.ops4j.pax.logging.cfg
+++ b/karaf/roboconf-karaf-dist-dm/src/main/resources/etc/org.ops4j.pax.logging.cfg
@@ -63,11 +63,11 @@ log4j.appender.jersey.layout.ConversionPattern=%d{ISO8601} | %-5.5p | %-32.32c{1
log4j.appender.jersey.file=${karaf.data}/log/jersey.log
log4j.appender.jersey.append=true
-# Silent very verbose loggers but let them associated with the "roboconf" appender
+# Silent very verbose loggers but let them associated with various appenders
log4j.logger.net.roboconf.dm.internal.tasks.CheckerForStoredMessagesTask=DEBUG, roboconf
log4j.logger.net.roboconf.dm.internal.tasks.CheckerForTargetsConfigurationTask=DEBUG, roboconf
log4j.logger.net.roboconf.dm.internal.tasks.CheckerForHeartbeatsTask=DEBUG, roboconf
log4j.logger.net.roboconf.dm.rest.services.internal.resources.impl.ApplicationResource=DEBUG, roboconf
log4j.logger.net.roboconf.target.api.AbstractThreadedTargetHandler$CheckingRunnable=DEBUG, roboconf
log4j.logger.net.roboconf.dm.internal.environment.messaging.DmMessageProcessor=DEBUG, roboconf
-log4j.org.apache.karaf.jaas.modules.audit.LogAuditLoginModule=WARN, karaf
+log4j.logger.org.apache.karaf.jaas.modules.audit.LogAuditLoginModule=WARN, karaf
diff --git a/miscellaneous/roboconf-dm-rest-client/pom.xml b/miscellaneous/roboconf-dm-rest-client/pom.xml
index 49432ba2..6a4c948f 100644
--- a/miscellaneous/roboconf-dm-rest-client/pom.xml
+++ b/miscellaneous/roboconf-dm-rest-client/pom.xml
@@ -170,6 +170,12 @@
4.3.1
provided
+
+
+ org.mockito
+ mockito-core
+ test
+
diff --git a/miscellaneous/roboconf-dm-rest-client/src/main/java/net/roboconf/dm/rest/client/WsClient.java b/miscellaneous/roboconf-dm-rest-client/src/main/java/net/roboconf/dm/rest/client/WsClient.java
index ff8b5a21..2ca0933a 100644
--- a/miscellaneous/roboconf-dm-rest-client/src/main/java/net/roboconf/dm/rest/client/WsClient.java
+++ b/miscellaneous/roboconf-dm-rest-client/src/main/java/net/roboconf/dm/rest/client/WsClient.java
@@ -25,6 +25,8 @@
package net.roboconf.dm.rest.client;
+import javax.ws.rs.core.Cookie;
+
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.WebResource;
@@ -32,11 +34,13 @@
import com.sun.jersey.api.client.config.DefaultClientConfig;
import net.roboconf.dm.rest.client.delegates.ApplicationWsDelegate;
+import net.roboconf.dm.rest.client.delegates.AuthenticationWsDelegate;
import net.roboconf.dm.rest.client.delegates.DebugWsDelegate;
import net.roboconf.dm.rest.client.delegates.ManagementWsDelegate;
import net.roboconf.dm.rest.client.delegates.PreferencesWsDelegate;
import net.roboconf.dm.rest.client.delegates.SchedulerWsDelegate;
import net.roboconf.dm.rest.client.delegates.TargetWsDelegate;
+import net.roboconf.dm.rest.commons.UrlConstants;
import net.roboconf.dm.rest.commons.json.ObjectMapperProvider;
/**
@@ -80,8 +84,10 @@ public class WsClient {
private final TargetWsDelegate targetWsDelegate;
private final SchedulerWsDelegate schedulerDelegate;
private final PreferencesWsDelegate preferencesWsDelegate;
+ private final AuthenticationWsDelegate authenticationWsDelegate;
private final Client client;
+ private String sessionId;
/**
@@ -98,12 +104,13 @@ public WsClient( String rootUrl ) {
this.client.setFollowRedirects( true );
WebResource resource = this.client.resource( rootUrl );
- this.applicationDelegate = new ApplicationWsDelegate( resource );
- this.managementDelegate = new ManagementWsDelegate( resource );
- this.debugDelegate = new DebugWsDelegate( resource );
- this.targetWsDelegate = new TargetWsDelegate( resource );
- this.schedulerDelegate = new SchedulerWsDelegate( resource );
- this.preferencesWsDelegate = new PreferencesWsDelegate( resource );
+ this.applicationDelegate = new ApplicationWsDelegate( resource, this );
+ this.managementDelegate = new ManagementWsDelegate( resource, this );
+ this.debugDelegate = new DebugWsDelegate( resource, this );
+ this.targetWsDelegate = new TargetWsDelegate( resource, this );
+ this.schedulerDelegate = new SchedulerWsDelegate( resource, this );
+ this.preferencesWsDelegate = new PreferencesWsDelegate( resource, this );
+ this.authenticationWsDelegate = new AuthenticationWsDelegate( resource, this );
}
@@ -157,10 +164,48 @@ public PreferencesWsDelegate getPreferencesWsDelegate() {
return this.preferencesWsDelegate;
}
+ /**
+ * @return the authenticationWsDelegate
+ */
+ public AuthenticationWsDelegate getAuthenticationWsDelegate() {
+ return this.authenticationWsDelegate;
+ }
+
/**
* @return the Jersey client (useful to configure it)
*/
public Client getJerseyClient() {
return this.client;
}
+
+ /**
+ * Sets the session ID to use when authenticated.
+ * @param sessionId
+ */
+ public void setSessionId( String sessionId ) {
+ this.sessionId = sessionId;
+ }
+
+ /**
+ * @return the sessionId
+ */
+ public String getSessionId() {
+ return this.sessionId;
+ }
+
+
+ /**
+ * @param resource a web resource
+ * @return a builder with an optional cookie (for authentication)
+ */
+ public WebResource.Builder createBuilder( WebResource resource ) {
+
+ WebResource.Builder result;
+ if( this.sessionId != null )
+ result = resource.cookie( new Cookie( UrlConstants.SESSION_ID, this.sessionId ));
+ else
+ result = resource.getRequestBuilder();
+
+ return result;
+ }
}
diff --git a/miscellaneous/roboconf-dm-rest-client/src/main/java/net/roboconf/dm/rest/client/delegates/ApplicationWsDelegate.java b/miscellaneous/roboconf-dm-rest-client/src/main/java/net/roboconf/dm/rest/client/delegates/ApplicationWsDelegate.java
index 7b36ae35..62f41ccb 100644
--- a/miscellaneous/roboconf-dm-rest-client/src/main/java/net/roboconf/dm/rest/client/delegates/ApplicationWsDelegate.java
+++ b/miscellaneous/roboconf-dm-rest-client/src/main/java/net/roboconf/dm/rest/client/delegates/ApplicationWsDelegate.java
@@ -39,6 +39,7 @@
import net.roboconf.core.model.beans.Component;
import net.roboconf.core.model.beans.Instance;
import net.roboconf.core.model.beans.Instance.InstanceStatus;
+import net.roboconf.dm.rest.client.WsClient;
import net.roboconf.dm.rest.client.exceptions.ApplicationWsException;
import net.roboconf.dm.rest.commons.UrlConstants;
@@ -49,14 +50,17 @@ public class ApplicationWsDelegate {
private final WebResource resource;
private final Logger logger;
+ private final WsClient wsClient;
/**
* Constructor.
* @param resource a web resource
+ * @param the WS client
*/
- public ApplicationWsDelegate( WebResource resource ) {
+ public ApplicationWsDelegate( WebResource resource, WsClient wsClient ) {
this.resource = resource;
+ this.wsClient = wsClient;
this.logger = Logger.getLogger( getClass().getName());
}
@@ -86,7 +90,10 @@ public void changeInstanceState( String applicationName, InstanceStatus newStatu
if( newStatus != null )
path = path.queryParam( "new-state", newStatus.toString());
- ClientResponse response = path.accept( MediaType.APPLICATION_JSON ).post( ClientResponse.class );
+ ClientResponse response = this.wsClient.createBuilder( path )
+ .accept( MediaType.APPLICATION_JSON )
+ .post( ClientResponse.class );
+
handleResponse( response );
this.logger.finer( String.valueOf( response.getStatusInfo()));
}
@@ -104,7 +111,9 @@ public void setDescription( String applicationName, String newDesc )
this.logger.finer( "Updating the description of application " + applicationName + "." );
WebResource path = this.resource.path( UrlConstants.APP ).path( applicationName ).path( "description" );
- ClientResponse response = path.accept( MediaType.TEXT_PLAIN ).post( ClientResponse.class, newDesc );
+ ClientResponse response = this.wsClient.createBuilder( path )
+ .accept( MediaType.TEXT_PLAIN )
+ .post( ClientResponse.class, newDesc );
handleResponse( response );
this.logger.finer( String.valueOf( response.getStatusInfo()));
@@ -126,7 +135,10 @@ public void deployAndStartAll( String applicationName, String instancePath )
if( instancePath != null )
path = path.queryParam( "instance-path", instancePath );
- ClientResponse response = path.accept( MediaType.APPLICATION_JSON ).post( ClientResponse.class );
+ ClientResponse response = this.wsClient.createBuilder( path )
+ .accept( MediaType.APPLICATION_JSON )
+ .post( ClientResponse.class );
+
handleResponse( response );
this.logger.finer( String.valueOf( response.getStatusInfo()));
}
@@ -147,7 +159,10 @@ public void stopAll( String applicationName, String instancePath )
if( instancePath != null )
path = path.queryParam( "instance-path", instancePath );
- ClientResponse response = path.accept( MediaType.APPLICATION_JSON ).post( ClientResponse.class );
+ ClientResponse response = this.wsClient.createBuilder( path )
+ .accept( MediaType.APPLICATION_JSON )
+ .post( ClientResponse.class );
+
handleResponse( response );
this.logger.finer( String.valueOf( response.getStatusInfo()));
}
@@ -168,7 +183,10 @@ public void undeployAll( String applicationName, String instancePath )
if( instancePath != null )
path = path.queryParam( "instance-path", instancePath );
- ClientResponse response = path.accept( MediaType.APPLICATION_JSON ).post( ClientResponse.class );
+ ClientResponse response = this.wsClient.createBuilder( path )
+ .accept( MediaType.APPLICATION_JSON )
+ .post( ClientResponse.class );
+
handleResponse( response );
this.logger.finer( String.valueOf( response.getStatusInfo()));
}
@@ -192,7 +210,8 @@ public List listChildrenInstances( String applicationName, String inst
path = path.queryParam( "instance-path", instancePath );
List result =
- path.accept( MediaType.APPLICATION_JSON )
+ this.wsClient.createBuilder( path )
+ .accept( MediaType.APPLICATION_JSON )
.type( MediaType.APPLICATION_JSON )
.get( new GenericType> () {});
@@ -221,7 +240,7 @@ public void addInstance( String applicationName, String parentInstancePath, Inst
if( parentInstancePath != null )
path = path.queryParam( "instance-path", parentInstancePath );
- ClientResponse response = path
+ ClientResponse response = this.wsClient.createBuilder( path )
.accept( MediaType.APPLICATION_JSON ).type( MediaType.APPLICATION_JSON )
.post( ClientResponse.class, instance );
@@ -240,13 +259,13 @@ public void removeInstance( String applicationName, String instancePath ) {
this.logger.finer( String.format( "Removing instance \"%s\" from application \"%s\"...",
instancePath, applicationName ) );
- this.resource
+ WebResource path = this.resource
.path( UrlConstants.APP )
.path( applicationName )
.path( "instances" )
- .queryParam( "instance-path", instancePath )
- .delete();
+ .queryParam( "instance-path", instancePath );
+ this.wsClient.createBuilder( path ).delete();
this.logger.finer( String.format( "Instance \"%s\" has been removed from application \"%s\"",
instancePath, applicationName ) );
}
@@ -260,12 +279,12 @@ public void removeInstance( String applicationName, String instancePath ) {
public void resynchronize( String applicationName ) {
this.logger.finer( String.format( "Resynchronizing application \"%s\"...", applicationName ) );
- this.resource
+ WebResource path = this.resource
.path( UrlConstants.APP )
.path( applicationName )
- .path( "resynchronize" )
- .post();
+ .path( "resynchronize" );
+ this.wsClient.createBuilder( path ).post();
this.logger.finer( String.format( "Application \"%s\" has been resynchronized", applicationName ) );
}
@@ -278,8 +297,9 @@ public void resynchronize( String applicationName ) {
public List listAllComponents( String applicationName ) {
this.logger.finer( "Listing components for application " + applicationName + "..." );
- List result = this.resource
- .path( UrlConstants.APP ).path( applicationName ).path( "components" )
+ WebResource path = this.resource.path( UrlConstants.APP ).path( applicationName ).path( "components" );
+ List result =
+ this.wsClient.createBuilder( path )
.accept( MediaType.APPLICATION_JSON )
.get( new GenericType> () {});
@@ -307,7 +327,10 @@ public List findComponentChildren( String applicationName, String com
if( componentName != null )
path = path.queryParam( "component-name", componentName );
- List result = path.accept( MediaType.APPLICATION_JSON ).get( new GenericType> () {});
+ List result = this.wsClient.createBuilder( path )
+ .accept( MediaType.APPLICATION_JSON )
+ .get( new GenericType> () {});
+
if( result != null ) {
this.logger.finer( result.size() + " possible children was or were found for " + componentName + "." );
} else {
@@ -332,7 +355,10 @@ public List findComponentAncestors( String applicationName, String co
if( componentName != null )
path = path.queryParam( "component-name", componentName );
- List result = path.accept( MediaType.APPLICATION_JSON ).get( new GenericType> () {});
+ List result = this.wsClient.createBuilder( path )
+ .accept( MediaType.APPLICATION_JSON )
+ .get( new GenericType> () {});
+
if( result != null ) {
this.logger.finer( result.size() + " possible parents was or were found for " + componentName + "." );
} else {
@@ -361,7 +387,7 @@ public void bindApplication( String applicationName, String boundTplName, String
.queryParam( "bound-tpl", boundTplName )
.queryParam( "bound-app", boundApp );
- ClientResponse response = path.post( ClientResponse.class );
+ ClientResponse response = this.wsClient.createBuilder( path ).post( ClientResponse.class );
handleResponse( response );
}
@@ -378,7 +404,10 @@ public List listAllCommands( String applicationName ) {
WebResource path = this.resource.path( UrlConstants.APP )
.path( applicationName ).path( "commands" );
- List result = path.accept( MediaType.APPLICATION_JSON ).get( new GenericType> () {});
+ List result = this.wsClient.createBuilder( path )
+ .accept( MediaType.APPLICATION_JSON )
+ .get( new GenericType> () {});
+
if( result != null ) {
this.logger.finer( result.size() + " command(s) were found for " + applicationName + "." );
} else {
diff --git a/miscellaneous/roboconf-dm-rest-client/src/main/java/net/roboconf/dm/rest/client/delegates/AuthenticationWsDelegate.java b/miscellaneous/roboconf-dm-rest-client/src/main/java/net/roboconf/dm/rest/client/delegates/AuthenticationWsDelegate.java
new file mode 100644
index 00000000..74385fed
--- /dev/null
+++ b/miscellaneous/roboconf-dm-rest-client/src/main/java/net/roboconf/dm/rest/client/delegates/AuthenticationWsDelegate.java
@@ -0,0 +1,118 @@
+/**
+ * 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.client.delegates;
+
+import java.util.List;
+import java.util.logging.Logger;
+
+import javax.ws.rs.core.NewCookie;
+import javax.ws.rs.core.Response.Status.Family;
+
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.WebResource;
+
+import net.roboconf.dm.rest.client.WsClient;
+import net.roboconf.dm.rest.client.exceptions.DebugWsException;
+import net.roboconf.dm.rest.commons.UrlConstants;
+
+/**
+ * @author Vincent Zurczak - Linagora
+ */
+public class AuthenticationWsDelegate {
+
+ private final WebResource resource;
+ private final WsClient wsClient;
+ private final Logger logger;
+
+
+ /**
+ * Constructor.
+ * @param resource a web resource
+ * @param the WS client
+ */
+ public AuthenticationWsDelegate( WebResource resource, WsClient wsClient ) {
+ this.resource = resource;
+ this.wsClient = wsClient;
+ this.logger = Logger.getLogger( getClass().getName());
+ }
+
+
+ /**
+ * Logs in with a user name and a password.
+ * @param username a user name
+ * @param password a password
+ * @return a session ID, or null if login failed
+ * @throws DebugWsException
+ */
+ public String login( String username, String password ) throws DebugWsException {
+
+ this.logger.finer( "Logging in as " + username );
+ WebResource path = this.resource.path( UrlConstants.AUTHENTICATION ).path( "e" );
+ ClientResponse response = path
+ .header( "u", username )
+ .header( "p", password )
+ .post( ClientResponse.class );
+
+ if( Family.SUCCESSFUL != response.getStatusInfo().getFamily()) {
+ String value = response.getEntity( String.class );
+ this.logger.finer( response.getStatusInfo() + ": " + value );
+ throw new DebugWsException( response.getStatusInfo().getStatusCode(), value );
+ }
+
+ // Get the session ID from the cookie that should have been returned
+ String sessionId = null;
+ List cookies = response.getCookies();
+ if( cookies != null ) {
+ for( NewCookie cookie : cookies ) {
+ if( UrlConstants.SESSION_ID.equals( cookie.getName())) {
+ sessionId = cookie.getValue();
+ break;
+ }
+ }
+ }
+
+ // Set the session ID
+ this.wsClient.setSessionId( sessionId );
+ this.logger.finer( "Session ID: " + sessionId );
+
+ return sessionId;
+ }
+
+
+ /**
+ * Terminates a session.
+ * @param sessionId a session ID
+ * @throws DebugWsException
+ */
+ public void logout( String sessionId ) throws DebugWsException {
+
+ this.logger.finer( "Logging out... Session ID = " + sessionId );
+ WebResource path = this.resource.path( UrlConstants.AUTHENTICATION ).path( "s" );
+ ClientResponse response = this.wsClient.createBuilder( path ).get( ClientResponse.class );
+ this.logger.finer( String.valueOf( response.getStatusInfo()));
+ this.wsClient.setSessionId( null );
+ }
+}
diff --git a/miscellaneous/roboconf-dm-rest-client/src/main/java/net/roboconf/dm/rest/client/delegates/DebugWsDelegate.java b/miscellaneous/roboconf-dm-rest-client/src/main/java/net/roboconf/dm/rest/client/delegates/DebugWsDelegate.java
index f098d37e..beca9995 100644
--- a/miscellaneous/roboconf-dm-rest-client/src/main/java/net/roboconf/dm/rest/client/delegates/DebugWsDelegate.java
+++ b/miscellaneous/roboconf-dm-rest-client/src/main/java/net/roboconf/dm/rest/client/delegates/DebugWsDelegate.java
@@ -31,14 +31,15 @@
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response.Status.Family;
-import net.roboconf.dm.rest.client.exceptions.DebugWsException;
-import net.roboconf.dm.rest.commons.Diagnostic;
-import net.roboconf.dm.rest.commons.UrlConstants;
-
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.GenericType;
import com.sun.jersey.api.client.WebResource;
+import net.roboconf.dm.rest.client.WsClient;
+import net.roboconf.dm.rest.client.exceptions.DebugWsException;
+import net.roboconf.dm.rest.commons.Diagnostic;
+import net.roboconf.dm.rest.commons.UrlConstants;
+
/**
* @author Vincent Zurczak - Linagora
*/
@@ -46,15 +47,17 @@ public class DebugWsDelegate {
private final WebResource resource;
private final Logger logger;
+ private final WsClient wsClient;
/**
* Constructor.
- *
* @param resource a web resource
+ * @param the WS client
*/
- public DebugWsDelegate( WebResource resource ) {
+ public DebugWsDelegate( WebResource resource, WsClient wsClient ) {
this.resource = resource;
+ this.wsClient = wsClient;
this.logger = Logger.getLogger( getClass().getName());
}
@@ -73,7 +76,7 @@ public String checkMessagingConnectionForTheDm( String message )
if( message != null )
path = path.queryParam( "message", message );
- ClientResponse response = path.get( ClientResponse.class );
+ ClientResponse response = this.wsClient.createBuilder( path ).get( ClientResponse.class );
if( Family.SUCCESSFUL != response.getStatusInfo().getFamily()) {
String value = response.getEntity( String.class );
this.logger.finer( response.getStatusInfo() + ": " + value );
@@ -105,7 +108,7 @@ public String checkMessagingConnectionWithAgent( String applicationName, String
if( message != null )
path = path.queryParam( "message", message );
- ClientResponse response = path.get( ClientResponse.class );
+ ClientResponse response = this.wsClient.createBuilder( path ).get( ClientResponse.class );
if( Family.SUCCESSFUL != response.getStatusInfo().getFamily()) {
String value = response.getEntity( String.class );
this.logger.finer( response.getStatusInfo() + ": " + value );
@@ -131,7 +134,10 @@ public Diagnostic diagnoseInstance( String applicationName, String instancePath
path = path.queryParam( "application-name", applicationName );
path = path.queryParam( "instance-path", instancePath );
- ClientResponse response = path.accept( MediaType.APPLICATION_JSON ).get( ClientResponse.class );
+ ClientResponse response = this.wsClient.createBuilder( path )
+ .accept( MediaType.APPLICATION_JSON )
+ .get( ClientResponse.class );
+
if( Family.SUCCESSFUL != response.getStatusInfo().getFamily()) {
String value = response.getEntity( String.class );
this.logger.finer( response.getStatusInfo() + ": " + value );
@@ -153,7 +159,10 @@ public List diagnoseApplication( String applicationName ) {
WebResource path = this.resource.path( UrlConstants.DEBUG ).path( "diagnose-application" );
path = path.queryParam( "application-name", applicationName );
- List result = path.accept( MediaType.APPLICATION_JSON ).get( new GenericType> () {});
+ List result = this.wsClient.createBuilder( path )
+ .accept( MediaType.APPLICATION_JSON )
+ .get( new GenericType> () {});
+
if( result == null )
this.logger.finer( "No diagnostic was returned for application " + applicationName );
diff --git a/miscellaneous/roboconf-dm-rest-client/src/main/java/net/roboconf/dm/rest/client/delegates/ManagementWsDelegate.java b/miscellaneous/roboconf-dm-rest-client/src/main/java/net/roboconf/dm/rest/client/delegates/ManagementWsDelegate.java
index 666e368c..9949ead8 100644
--- a/miscellaneous/roboconf-dm-rest-client/src/main/java/net/roboconf/dm/rest/client/delegates/ManagementWsDelegate.java
+++ b/miscellaneous/roboconf-dm-rest-client/src/main/java/net/roboconf/dm/rest/client/delegates/ManagementWsDelegate.java
@@ -43,6 +43,7 @@
import net.roboconf.core.model.beans.Application;
import net.roboconf.core.model.beans.ApplicationTemplate;
+import net.roboconf.dm.rest.client.WsClient;
import net.roboconf.dm.rest.client.exceptions.ManagementWsException;
import net.roboconf.dm.rest.commons.UrlConstants;
@@ -53,14 +54,17 @@ public class ManagementWsDelegate {
private final WebResource resource;
private final Logger logger;
+ private final WsClient wsClient;
/**
* Constructor.
* @param resource a web resource
+ * @param the WS client
*/
- public ManagementWsDelegate( WebResource resource ) {
+ public ManagementWsDelegate( WebResource resource, WsClient wsClient ) {
this.resource = resource;
+ this.wsClient = wsClient;
this.logger = Logger.getLogger( getClass().getName());
}
@@ -86,8 +90,8 @@ public void uploadZippedApplicationTemplate( File applicationFile ) throws Manag
FormDataMultiPart part = new FormDataMultiPart();
part.bodyPart( new FileDataBodyPart( "file", applicationFile, MediaType.APPLICATION_OCTET_STREAM_TYPE));
- ClientResponse response = this.resource
- .path( UrlConstants.APPLICATIONS ).path( "templates" )
+ WebResource path = this.resource.path( UrlConstants.APPLICATIONS ).path( "templates" );
+ ClientResponse response = this.wsClient.createBuilder( path )
.type( MediaType.MULTIPART_FORM_DATA_TYPE )
.post( ClientResponse.class, part );
@@ -113,7 +117,10 @@ public void loadUnzippedApplicationTemplate( String localFilePath ) throws Manag
if( localFilePath != null )
path = path.queryParam( "local-file-path", localFilePath );
- ClientResponse response = path.type( MediaType.APPLICATION_JSON ).post( ClientResponse.class );
+ ClientResponse response = this.wsClient.createBuilder( path )
+ .type( MediaType.APPLICATION_JSON )
+ .post( ClientResponse.class );
+
if( Family.SUCCESSFUL != response.getStatusInfo().getFamily()) {
String value = response.getEntity( String.class );
this.logger.finer( response.getStatusInfo() + ": " + value );
@@ -136,7 +143,10 @@ public void loadZippedApplicationTemplate( String url ) throws ManagementWsExcep
if( url != null )
path = path.queryParam( "url", url );
- ClientResponse response = path.type( MediaType.APPLICATION_JSON ).post( ClientResponse.class );
+ ClientResponse response = this.wsClient.createBuilder( path )
+ .type( MediaType.APPLICATION_JSON )
+ .post( ClientResponse.class );
+
if( Family.SUCCESSFUL != response.getStatusInfo().getFamily()) {
String value = response.getEntity( String.class );
this.logger.finer( response.getStatusInfo() + ": " + value );
@@ -195,7 +205,7 @@ public List listApplicationTemplates( String exactName, Str
if( exactQualifier != null )
path = path.queryParam( "qualifier", exactQualifier );
- List result = path
+ List result = this.wsClient.createBuilder( path )
.accept( MediaType.APPLICATION_JSON )
.get( new GenericType> () {});
@@ -231,11 +241,11 @@ public List listApplicationTemplates() throws ManagementWsE
public void deleteApplicationTemplate( String templateName, String templateQualifier ) throws ManagementWsException {
this.logger.finer( "Removing application template " + templateName + "..." );
- ClientResponse response = this.resource
+ WebResource path = this.resource
.path( UrlConstants.APPLICATIONS ).path( "templates" )
- .path( templateName ).path( templateQualifier )
- .delete( ClientResponse.class );
+ .path( templateName ).path( templateQualifier );
+ ClientResponse response = this.wsClient.createBuilder( path ).delete( ClientResponse.class );
String text = response.getEntity( String.class );
this.logger.finer( text );
if( Family.SUCCESSFUL != response.getStatusInfo().getFamily())
@@ -261,8 +271,8 @@ public Application createApplication( String applicationName, String templateNam
ApplicationTemplate tpl = new ApplicationTemplate( templateName ).qualifier( templateQualifier );
Application app = new Application( applicationName, tpl );
- ClientResponse response = this.resource
- .path( UrlConstants.APPLICATIONS )
+ WebResource path = this.resource.path( UrlConstants.APPLICATIONS );
+ ClientResponse response = this.wsClient.createBuilder( path )
.type( MediaType.APPLICATION_JSON )
.post( ClientResponse.class, app );
@@ -291,7 +301,7 @@ public List listApplications( String exactName ) throws ManagementW
if( exactName != null )
path = path.queryParam( "name", exactName );
- List result = path
+ List result = this.wsClient.createBuilder( path )
.accept( MediaType.APPLICATION_JSON )
.get( new GenericType> () {});
@@ -326,10 +336,12 @@ public List listApplications() throws ManagementWsException {
public void shutdownApplication( String applicationName ) throws ManagementWsException {
this.logger.finer( "Removing application " + applicationName + "..." );
- ClientResponse response = this.resource
- .path( UrlConstants.APPLICATIONS ).path( applicationName ).path( "shutdown" )
- .post( ClientResponse.class );
+ WebResource path = this.resource
+ .path( UrlConstants.APPLICATIONS )
+ .path( applicationName )
+ .path( "shutdown" );
+ ClientResponse response = this.wsClient.createBuilder( path ).post( ClientResponse.class );
String text = response.getEntity( String.class );
this.logger.finer( text );
if( Family.SUCCESSFUL != response.getStatusInfo().getFamily())
@@ -345,9 +357,8 @@ public void shutdownApplication( String applicationName ) throws ManagementWsExc
public void deleteApplication( String applicationName ) throws ManagementWsException {
this.logger.finer( "Removing application " + applicationName + "..." );
- ClientResponse response = this.resource
- .path( UrlConstants.APPLICATIONS ).path( applicationName )
- .delete( ClientResponse.class );
+ WebResource path = this.resource.path( UrlConstants.APPLICATIONS ).path( applicationName );
+ ClientResponse response = this.wsClient.createBuilder( path ).delete( ClientResponse.class );
String text = response.getEntity( String.class );
this.logger.finer( text );
diff --git a/miscellaneous/roboconf-dm-rest-client/src/main/java/net/roboconf/dm/rest/client/delegates/PreferencesWsDelegate.java b/miscellaneous/roboconf-dm-rest-client/src/main/java/net/roboconf/dm/rest/client/delegates/PreferencesWsDelegate.java
index 73bd7404..58096412 100644
--- a/miscellaneous/roboconf-dm-rest-client/src/main/java/net/roboconf/dm/rest/client/delegates/PreferencesWsDelegate.java
+++ b/miscellaneous/roboconf-dm-rest-client/src/main/java/net/roboconf/dm/rest/client/delegates/PreferencesWsDelegate.java
@@ -35,6 +35,7 @@
import com.sun.jersey.api.client.WebResource;
import net.roboconf.core.model.runtime.Preference;
+import net.roboconf.dm.rest.client.WsClient;
import net.roboconf.dm.rest.commons.UrlConstants;
/**
@@ -44,14 +45,17 @@ public class PreferencesWsDelegate {
private final WebResource resource;
private final Logger logger;
+ private final WsClient wsClient;
/**
* Constructor.
* @param resource a web resource
+ * @param the WS client
*/
- public PreferencesWsDelegate( WebResource resource ) {
+ public PreferencesWsDelegate( WebResource resource, WsClient wsClient ) {
this.resource = resource;
+ this.wsClient = wsClient;
this.logger = Logger.getLogger( getClass().getName());
}
@@ -64,7 +68,7 @@ public List listPreferences() {
this.logger.finer( "Getting all the preferences." );
WebResource path = this.resource.path( UrlConstants.PREFERENCES );
- List result = path
+ List result = this.wsClient.createBuilder( path )
.accept( MediaType.APPLICATION_JSON )
.get( new GenericType> () {});
diff --git a/miscellaneous/roboconf-dm-rest-client/src/main/java/net/roboconf/dm/rest/client/delegates/SchedulerWsDelegate.java b/miscellaneous/roboconf-dm-rest-client/src/main/java/net/roboconf/dm/rest/client/delegates/SchedulerWsDelegate.java
index b26b06a5..25a6dc46 100644
--- a/miscellaneous/roboconf-dm-rest-client/src/main/java/net/roboconf/dm/rest/client/delegates/SchedulerWsDelegate.java
+++ b/miscellaneous/roboconf-dm-rest-client/src/main/java/net/roboconf/dm/rest/client/delegates/SchedulerWsDelegate.java
@@ -37,6 +37,7 @@
import com.sun.jersey.api.client.WebResource;
import net.roboconf.core.model.runtime.ScheduledJob;
+import net.roboconf.dm.rest.client.WsClient;
import net.roboconf.dm.rest.client.exceptions.SchedulerWsException;
import net.roboconf.dm.rest.commons.UrlConstants;
import net.roboconf.dm.rest.commons.json.StringWrapper;
@@ -48,14 +49,17 @@ public class SchedulerWsDelegate {
private final WebResource resource;
private final Logger logger;
+ private final WsClient wsClient;
/**
* Constructor.
* @param resource a web resource
+ * @param the WS client
*/
- public SchedulerWsDelegate( WebResource resource ) {
+ public SchedulerWsDelegate( WebResource resource, WsClient wsClient ) {
this.resource = resource;
+ this.wsClient = wsClient;
this.logger = Logger.getLogger( getClass().getName());
}
@@ -98,7 +102,8 @@ public List listAllJobs( String appName, String cmdName ) {
path = path.queryParam( "cmd-name", cmdName );
List result =
- path.accept( MediaType.APPLICATION_JSON )
+ this.wsClient.createBuilder( path )
+ .accept( MediaType.APPLICATION_JSON )
.type( MediaType.APPLICATION_JSON )
.get( new GenericType> () {});
@@ -106,7 +111,7 @@ public List listAllJobs( String appName, String cmdName ) {
this.logger.finer( result.size() + " jobs were found." );
} else {
this.logger.finer( "No scheduled job was found." );
- result = new ArrayList( 0 );
+ result = new ArrayList<>( 0 );
}
return result;
@@ -140,9 +145,11 @@ public String createOrUpdateJob( String jobId, String jobName, String appName, S
path = path.queryParam( "cmd-name", cmdName );
path = path.queryParam( "cron", cron );
- ClientResponse response = path.accept( MediaType.APPLICATION_JSON ).post( ClientResponse.class );
- handleResponse( response );
+ ClientResponse response = this.wsClient.createBuilder( path )
+ .accept( MediaType.APPLICATION_JSON )
+ .post( ClientResponse.class );
+ handleResponse( response );
StringWrapper wrapper = response.getEntity( StringWrapper.class );
return wrapper.toString();
}
@@ -158,7 +165,10 @@ public void deleteJob( String jobId ) throws SchedulerWsException {
this.logger.finer( "Deleting scheduled job: " + jobId );
WebResource path = this.resource.path( UrlConstants.SCHEDULER ).path( jobId );
- ClientResponse response = path.accept( MediaType.APPLICATION_JSON ).delete( ClientResponse.class );
+ ClientResponse response = this.wsClient.createBuilder( path )
+ .accept( MediaType.APPLICATION_JSON )
+ .delete( ClientResponse.class );
+
handleResponse( response );
}
@@ -173,9 +183,11 @@ public ScheduledJob getJobProperties( String jobId ) throws SchedulerWsException
this.logger.finer( "Getting the properties of a scheduled job: " + jobId );
WebResource path = this.resource.path( UrlConstants.SCHEDULER ).path( jobId );
- ClientResponse response = path.accept( MediaType.APPLICATION_JSON ).get( ClientResponse.class );
- handleResponse( response );
+ ClientResponse response = this.wsClient.createBuilder( path )
+ .accept( MediaType.APPLICATION_JSON )
+ .get( ClientResponse.class );
+ handleResponse( response );
return response.getEntity( ScheduledJob.class );
}
diff --git a/miscellaneous/roboconf-dm-rest-client/src/main/java/net/roboconf/dm/rest/client/delegates/TargetWsDelegate.java b/miscellaneous/roboconf-dm-rest-client/src/main/java/net/roboconf/dm/rest/client/delegates/TargetWsDelegate.java
index 647c12e1..30ae6539 100644
--- a/miscellaneous/roboconf-dm-rest-client/src/main/java/net/roboconf/dm/rest/client/delegates/TargetWsDelegate.java
+++ b/miscellaneous/roboconf-dm-rest-client/src/main/java/net/roboconf/dm/rest/client/delegates/TargetWsDelegate.java
@@ -39,6 +39,7 @@
import net.roboconf.core.model.beans.AbstractApplication;
import net.roboconf.core.model.beans.ApplicationTemplate;
import net.roboconf.core.model.runtime.TargetWrapperDescriptor;
+import net.roboconf.dm.rest.client.WsClient;
import net.roboconf.dm.rest.client.exceptions.TargetWsException;
import net.roboconf.dm.rest.commons.UrlConstants;
@@ -50,14 +51,17 @@ public class TargetWsDelegate {
private final WebResource resource;
private final Logger logger;
+ private final WsClient wsClient;
/**
* Constructor.
* @param resource a web resource
+ * @param the WS client
*/
- public TargetWsDelegate( WebResource resource ) {
+ public TargetWsDelegate( WebResource resource, WsClient wsClient ) {
this.resource = resource;
+ this.wsClient = wsClient;
this.logger = Logger.getLogger( getClass().getName());
}
@@ -71,7 +75,8 @@ public List listAllTargets() {
this.logger.finer( "Listing all the available targets." );
WebResource path = this.resource.path( UrlConstants.TARGETS );
List result =
- path.accept( MediaType.APPLICATION_JSON )
+ this.wsClient.createBuilder( path )
+ .accept( MediaType.APPLICATION_JSON )
.type( MediaType.APPLICATION_JSON )
.get( new GenericType> () {});
@@ -95,7 +100,7 @@ public String createTarget( String targetContent ) throws TargetWsException {
this.logger.finer( "Creating a new target." );
WebResource path = this.resource.path( UrlConstants.TARGETS );
- ClientResponse response = path.post( ClientResponse.class, targetContent );
+ ClientResponse response = this.wsClient.createBuilder( path ).post( ClientResponse.class, targetContent );
handleResponse( response );
return response.getEntity( String.class );
@@ -112,7 +117,10 @@ public void deleteTarget( String targetId ) throws TargetWsException {
this.logger.finer( "Deleting target " + targetId );
WebResource path = this.resource.path( UrlConstants.TARGETS ).path( targetId );
- ClientResponse response = path.accept( MediaType.APPLICATION_JSON ).delete( ClientResponse.class );
+ ClientResponse response = this.wsClient.createBuilder( path )
+ .accept( MediaType.APPLICATION_JSON )
+ .delete( ClientResponse.class );
+
handleResponse( response );
}
@@ -144,7 +152,10 @@ public void associateTarget( AbstractApplication app, String instancePathOrCompo
if( app instanceof ApplicationTemplate )
path = path.queryParam( "qualifier", ((ApplicationTemplate) app).getQualifier());
- ClientResponse response = path.accept( MediaType.APPLICATION_JSON ).post( ClientResponse.class );
+ ClientResponse response = this.wsClient.createBuilder( path )
+ .accept( MediaType.APPLICATION_JSON )
+ .post( ClientResponse.class );
+
handleResponse( response );
}
diff --git a/miscellaneous/roboconf-dm-rest-client/src/test/java/net/roboconf/dm/rest/client/delegates/AuthenticationWsDelegateTest.java b/miscellaneous/roboconf-dm-rest-client/src/test/java/net/roboconf/dm/rest/client/delegates/AuthenticationWsDelegateTest.java
new file mode 100644
index 00000000..b6f6fe6d
--- /dev/null
+++ b/miscellaneous/roboconf-dm-rest-client/src/test/java/net/roboconf/dm/rest/client/delegates/AuthenticationWsDelegateTest.java
@@ -0,0 +1,105 @@
+/**
+ * Copyright 2014-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.client.delegates;
+
+import java.net.URI;
+
+import javax.security.auth.login.LoginException;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import com.sun.jersey.api.container.grizzly2.GrizzlyServerFactory;
+
+import net.roboconf.dm.rest.client.WsClient;
+import net.roboconf.dm.rest.client.exceptions.DebugWsException;
+import net.roboconf.dm.rest.commons.security.AuthenticationManager;
+import net.roboconf.dm.rest.commons.security.AuthenticationManager.IAuthService;
+import net.roboconf.dm.rest.services.internal.RestApplication;
+
+/**
+ * @author Vincent Zurczak - Linagora
+ */
+public class AuthenticationWsDelegateTest {
+
+ private static final String REST_URI = "http://localhost:8090";
+
+ private WsClient client;
+ private HttpServer httpServer;
+ private IAuthService authService;
+
+
+ @After
+ public void after() {
+
+ if( this.httpServer != null )
+ this.httpServer.stop();
+
+ if( this.client != null )
+ this.client.destroy();
+ }
+
+
+ @Before
+ public void before() throws Exception {
+
+ URI uri = UriBuilder.fromUri( REST_URI ).build();
+ RestApplication restApp = new RestApplication( null );
+
+ AuthenticationManager authenticationMngr = new AuthenticationManager( "whatever" );
+ this.authService = Mockito.mock( IAuthService.class );
+ authenticationMngr.setAuthService( this.authService );
+ restApp.setAuthenticationManager( authenticationMngr );
+
+ this.httpServer = GrizzlyServerFactory.createHttpServer( uri, restApp );
+ this.client = new WsClient( REST_URI );
+ }
+
+
+ @Test( expected = DebugWsException.class )
+ public void testLogin_failure() throws Exception {
+
+ Mockito.doThrow( new LoginException( "for test" )).when( this.authService ).authenticate( "u", "p" );
+ this.client.getAuthenticationWsDelegate().login( "u", "p" );
+ }
+
+
+ @Test
+ public void testLogin_success() throws Exception {
+
+ String sessionId = this.client.getAuthenticationWsDelegate().login( "u", "p" );
+ Assert.assertNotNull( sessionId );
+ this.client.getAuthenticationWsDelegate().logout( sessionId );
+
+ // Log out twice does not result in any error
+ this.client.getAuthenticationWsDelegate().logout( sessionId );
+ }
+}
diff --git a/miscellaneous/roboconf-dm-rest-client/src/test/java/net/roboconf/dm/rest/client/delegates/ManagementWsDelegateTest.java b/miscellaneous/roboconf-dm-rest-client/src/test/java/net/roboconf/dm/rest/client/delegates/ManagementWsDelegateTest.java
index 62121ad6..5a43b35e 100644
--- a/miscellaneous/roboconf-dm-rest-client/src/test/java/net/roboconf/dm/rest/client/delegates/ManagementWsDelegateTest.java
+++ b/miscellaneous/roboconf-dm-rest-client/src/test/java/net/roboconf/dm/rest/client/delegates/ManagementWsDelegateTest.java
@@ -152,6 +152,12 @@ public void testListApplications() throws Exception {
apps = this.client.getManagementDelegate().listApplications( app.getName() + "0" );
Assert.assertNotNull( apps );
Assert.assertEquals( 0, apps.size());
+
+ // Get when a session ID was set
+ this.client.setSessionId( "whatever, filtering is not enabled during these tests" );
+ apps = this.client.getManagementDelegate().listApplications();
+ Assert.assertNotNull( apps );
+ Assert.assertEquals( 1, apps.size());
}
diff --git a/miscellaneous/roboconf-integration-tests-dm-with-agents-in-memory/src/test/java/net/roboconf/integration/tests/dm/with/agents/in/memory/RestSecuredServicesTest.java b/miscellaneous/roboconf-integration-tests-dm-with-agents-in-memory/src/test/java/net/roboconf/integration/tests/dm/with/agents/in/memory/RestSecuredServicesTest.java
new file mode 100644
index 00000000..72897e5b
--- /dev/null
+++ b/miscellaneous/roboconf-integration-tests-dm-with-agents-in-memory/src/test/java/net/roboconf/integration/tests/dm/with/agents/in/memory/RestSecuredServicesTest.java
@@ -0,0 +1,152 @@
+/**
+ * Copyright 2014-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.integration.tests.dm.with.agents.in.memory;
+
+import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.editConfigurationFilePut;
+
+import java.io.File;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Test;
+import org.ops4j.pax.exam.ExamSystem;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.TestContainer;
+import org.ops4j.pax.exam.karaf.container.internal.KarafTestContainer;
+import org.ops4j.pax.exam.spi.PaxExamRuntime;
+
+import net.roboconf.core.internal.tests.TestUtils;
+import net.roboconf.core.model.beans.ApplicationTemplate;
+import net.roboconf.dm.rest.client.WsClient;
+import net.roboconf.integration.tests.commons.internal.ItUtils;
+import net.roboconf.integration.tests.dm.with.agents.in.memory.probes.DmWithAgentInMemoryTest;
+import net.roboconf.messaging.rabbitmq.internal.utils.RabbitMqTestUtils;
+
+/**
+ * This test verifies that a client can interact with the DM's REST services.
+ * @author Vincent Zurczak - Linagora
+ */
+public class RestSecuredServicesTest extends DmWithAgentInMemoryTest {
+
+ private File karafDirectory;
+
+
+ @Override
+ public Option[] config() throws Exception {
+
+ List options = getOptionsForInMemoryAsList();
+ options.add( editConfigurationFilePut(
+ "etc/net.roboconf.dm.rest.services.configuration.cfg",
+ "enable-authentication",
+ "true" ));
+
+ options.add( editConfigurationFilePut(
+ "etc/net.roboconf.dm.rest.services.configuration.cfg",
+ "authentication-realm",
+ "karaf" ));
+
+ return ItUtils.asArray( options );
+ }
+
+
+ @Test
+ public void run() throws Exception {
+
+ Assume.assumeTrue( RabbitMqTestUtils.checkRabbitMqIsRunning());
+ File appDirectory = TestUtils.findApplicationDirectory( "lamp" );
+
+ // Prepare to run an agent distribution
+ ExamSystem system = PaxExamRuntime.createServerSystem( config());
+ TestContainer container = PaxExamRuntime.createContainer( system );
+ Assert.assertEquals( KarafTestContainer.class, container.getClass());
+
+ WsClient client = null;
+ try {
+ // Start the DM's distribution... and wait... :(
+ container.start();
+ ItUtils.waitForDmRestServices( getCurrentPort());
+
+ // Find the Karaf directory
+ this.karafDirectory = TestUtils.getInternalField( container, "targetFolder", File.class );
+ Assert.assertNotNull( this.karafDirectory );
+
+ // Build a REST client
+ String rootUrl = "http://localhost:" + getCurrentPort() + "/roboconf-dm";
+ client = new WsClient( rootUrl );
+
+ // Perform the checks
+ testRestInteractions( appDirectory.getAbsolutePath(), rootUrl, client );
+
+ } finally {
+ container.stop();
+ if( client != null )
+ client.destroy();
+ }
+ }
+
+
+ private void testRestInteractions( String appLocation, String rootUrl, WsClient client )
+ throws Exception {
+
+ // Not logged in
+ try {
+ client.getManagementDelegate().listApplicationTemplates();
+ Assert.fail( "An exception was expected, the user is not logged in." );
+
+ } catch( Exception e ) {
+ // nothing
+ }
+
+ // Log in
+ String sessionId = client.getAuthenticationWsDelegate().login( "karaf", "karaf" );
+ Assert.assertNotNull( sessionId );
+ Assert.assertEquals( sessionId, client.getSessionId());
+
+ // Load an application template
+ Assert.assertEquals( 0, client.getManagementDelegate().listApplicationTemplates().size());
+ client.getManagementDelegate().loadUnzippedApplicationTemplate( appLocation );
+ List templates = client.getManagementDelegate().listApplicationTemplates();
+ Assert.assertEquals( 1, templates.size());
+
+ ApplicationTemplate tpl = templates.get( 0 );
+ Assert.assertEquals( "Legacy LAMP", tpl.getName());
+ Assert.assertEquals( "sample", tpl.getQualifier());
+
+ // Log out
+ client.getAuthenticationWsDelegate().logout( sessionId );
+ Assert.assertNull( client.getSessionId());
+
+ // We cannot retrieve anything else
+ try {
+ client.getManagementDelegate().listApplicationTemplates();
+ Assert.fail( "An exception was expected, the user is not logged in." );
+
+ } catch( Exception e ) {
+ // nothing
+ }
+ }
+}