diff --git a/pom.xml b/pom.xml
index d3a2f06d..981de3d1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -329,6 +329,12 @@
redback-common-configuration-api
${project.version}
+
+ org.apache.archiva.redback
+ redback-authentication-jwt
+ ${project.version}
+
+
commons-logging
@@ -421,6 +427,20 @@
${slf4j.version}
test
+
+
+ org.mockito
+ mockito-core
+ ${mockito.version}
+ test
+
+
+ org.mockito
+ mockito-junit-jupiter
+ ${mockito.version}
+ test
+
+
org.easymock
easymock
@@ -537,6 +557,12 @@
jackson-databind
${jackson.version}
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-xml
+ ${jackson.version}
+
+
org.apache.cxf
diff --git a/redback-authentication/redback-authentication-api/pom.xml b/redback-authentication/redback-authentication-api/pom.xml
index cf3629cd..dad8ad31 100644
--- a/redback-authentication/redback-authentication-api/pom.xml
+++ b/redback-authentication/redback-authentication-api/pom.xml
@@ -41,6 +41,10 @@
org.apache.archiva.redback
redback-users-api
+
+ org.apache.archiva.redback
+ redback-configuration
+
jakarta.inject
diff --git a/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/AuthenticationConstants.java b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/AuthenticationConstants.java
index 836e7f2f..94c85966 100644
--- a/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/AuthenticationConstants.java
+++ b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/AuthenticationConstants.java
@@ -44,4 +44,5 @@ public class AuthenticationConstants
*/
public static final int AUTHN_MUST_CHANGE_PASSWORD_EXCEPTION = 4;
+
}
diff --git a/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/AuthenticationControl.java b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/AuthenticationControl.java
new file mode 100644
index 00000000..6a90effe
--- /dev/null
+++ b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/AuthenticationControl.java
@@ -0,0 +1,63 @@
+package org.apache.archiva.redback.authentication;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+/**
+ * These have the same meaning as for PAM modules
+ *
+ *
+ * - required
+ * - If a ‘required’ module returns a status that is not ‘success’,
+ * the operation will ultimately fail, but only after the modules below
+ * it are invoked. This seems senseless at first glance I suppose, but
+ * it serves the purpose of always acting the same way from the point
+ * of view of the user trying to utilize the service. The net effect is
+ * that it becomes impossible for a potential cracker to determine
+ * which module caused the failure – and the less information a
+ * malicious user has about your system, the better. Important to note
+ * is that even if all of the modules in the stack succeed, failure of
+ * one ‘required’ module means the operation will ultimately fail. Of
+ * course, if a required module succeeds, the operation can still fail
+ * if a ‘required’ module later in the stack fails.
+ * - requisite
+ * - If a ‘requisite’ module fails, the operation not only fails, but
+ * the operation is immediately terminated with a failure without
+ * invoking any other modules: ‘do not pass go, do not collect $200’,
+ * so to speak.
+ * - sufficient
+ * - If a sufficient module succeeds, it is enough to satisfy the
+ * requirements of sufficient modules in that realm for use of the
+ * service, and modules below it that are also listed as ‘sufficient’
+ * are not invoked. If it fails, the operation fails unless a module
+ * invoked after it succeeds. Important to note is that if a ‘required’
+ * module fails before a ‘sufficient’ one succeeds, the operation will
+ * fail anyway, ignoring the status of any ‘sufficient’ modules.
+ * - optional
+ * - An ‘optional’ module, according to the pam(8) manpage, will only
+ * cause an operation to fail if it’s the only module in the stack for
+ * that facility.
+ *
+ *
+ * @author Martin Stockhammer
+ * @since 3.0
+ */
+public enum AuthenticationControl
+{
+ SUFFICIENT, OPTIONAL, REQUIRED, REQUISITE
+}
diff --git a/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/AuthenticationManager.java b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/AuthenticationManager.java
index 96413aca..0780228a 100644
--- a/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/AuthenticationManager.java
+++ b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/AuthenticationManager.java
@@ -27,14 +27,50 @@
/**
* AuthenticationManager:
*
- * @author: Jesse McConnell
+ * @author Jesse McConnell
+ * @author Martin Stockhammer
*/
public interface AuthenticationManager
{
+ /**
+ * Returns the identifier of this authentication manager
+ * @return the identifier string
+ */
String getId();
+ /**
+ * Returns the list of authenticators in the same order as they are called for authentication
+ * @return the list of authenticators.
+ */
List getAuthenticators();
+ /**
+ * Authenticates by calling all authenticators in the defined order.
+ *
+ * @param source the authentication data
+ * @return the result that gives information, if the authentication was successful
+ * @throws AccountLockedException if the account is locked
+ * @throws AuthenticationException if something unexpected happend during authentication
+ * @throws MustChangePasswordException if the user has to change his password
+ */
AuthenticationResult authenticate( AuthenticationDataSource source )
throws AccountLockedException, AuthenticationException, MustChangePasswordException;
+
+ /**
+ * Returns the authenticator controls that are used to control the order and actions during authentication.
+ * @return the list of controls
+ */
+ List getControls();
+
+ /**
+ * Sets the list of authenticator controls
+ * @param controlList the list of control instances
+ */
+ void setControls( List controlList);
+
+ /**
+ * Modifies the control for a single authenticator
+ * @param control the authenticator control
+ */
+ void modifyControl(AuthenticatorControl control);
}
\ No newline at end of file
diff --git a/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/AuthenticationResult.java b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/AuthenticationResult.java
index 6c56f2f1..cc02a3d0 100644
--- a/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/AuthenticationResult.java
+++ b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/AuthenticationResult.java
@@ -96,7 +96,7 @@ public List getAuthenticationFailureCauses()
{
if ( authenticationFailureCauses == null )
{
- this.authenticationFailureCauses = new ArrayList();
+ this.authenticationFailureCauses = new ArrayList<>( );
}
return authenticationFailureCauses;
}
diff --git a/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/AuthenticatorControl.java b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/AuthenticatorControl.java
new file mode 100644
index 00000000..78e351ab
--- /dev/null
+++ b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/AuthenticatorControl.java
@@ -0,0 +1,76 @@
+package org.apache.archiva.redback.authentication;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+/**
+ * Gives a priority and what to do, if the authentication succeeds.
+ *
+ * @author Martin Stockhammer
+ * @since 3.0
+ */
+public class AuthenticatorControl implements Comparable
+{
+ final String name;
+ final int priority;
+ final boolean active;
+ final AuthenticationControl control;
+
+ public AuthenticatorControl( String name, int priority, AuthenticationControl control )
+ {
+ this.name = name;
+ this.priority = priority;
+ this.control = control;
+ this.active = true;
+ }
+
+ public AuthenticatorControl( String name, int priority, AuthenticationControl control, boolean active)
+ {
+ this.name = name;
+ this.priority = priority;
+ this.control = control;
+ this.active = active;
+ }
+
+
+ public String getName( )
+ {
+ return name;
+ }
+
+ public int getPriority( )
+ {
+ return priority;
+ }
+
+ public AuthenticationControl getControl( )
+ {
+ return control;
+ }
+
+ @Override
+ public int compareTo( AuthenticatorControl o )
+ {
+ return this.getPriority()-o.getPriority();
+ }
+
+ public boolean isActive( )
+ {
+ return active;
+ }
+}
diff --git a/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/BearerTokenAuthenticationDataSource.java b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/BearerTokenAuthenticationDataSource.java
new file mode 100644
index 00000000..f7139172
--- /dev/null
+++ b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/BearerTokenAuthenticationDataSource.java
@@ -0,0 +1,64 @@
+package org.apache.archiva.redback.authentication;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+/**
+ * Datasource used for authentication by Bearer token (JWT)
+ *
+ * @author Martin Stockhammer
+ * @since 3.0
+ */
+public class BearerTokenAuthenticationDataSource implements AuthenticationDataSource
+{
+ private String tokenData;
+ private String principal;
+
+ public BearerTokenAuthenticationDataSource( )
+ {
+ }
+
+ public BearerTokenAuthenticationDataSource( String principal, String tokenData )
+ {
+ this.tokenData = tokenData;
+ this.principal = principal;
+ }
+
+ @Override
+ public String getUsername( )
+ {
+ return principal;
+ }
+
+ @Override
+ public boolean isEnforcePasswordChange( )
+ {
+ return false;
+ }
+
+ public String getTokenData( )
+ {
+ return tokenData;
+ }
+
+ public void setTokenData( String tokenData )
+ {
+ this.tokenData = tokenData;
+ }
+
+}
diff --git a/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/DefaultAuthenticationManager.java b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/DefaultAuthenticationManager.java
index 1bc47707..a8c09f2f 100644
--- a/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/DefaultAuthenticationManager.java
+++ b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/DefaultAuthenticationManager.java
@@ -19,6 +19,7 @@
* under the License.
*/
+import org.apache.archiva.redback.configuration.UserConfiguration;
import org.apache.archiva.redback.policy.AccountLockedException;
import org.apache.archiva.redback.policy.MustChangePasswordException;
import org.apache.archiva.redback.users.User;
@@ -33,9 +34,18 @@
import javax.inject.Inject;
import javax.inject.Named;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
/**
@@ -48,51 +58,118 @@
*
* @author: Jesse McConnell
*/
-@Service("authenticationManager")
+@Service( "authenticationManager" )
public class DefaultAuthenticationManager
- implements AuthenticationManager {
+ implements AuthenticationManager
+{
- private Logger log = LoggerFactory.getLogger(getClass());
+ private static final Logger log = LoggerFactory.getLogger( DefaultAuthenticationManager.class );
- private List authenticators;
+ final private AtomicReference> authenticators = new AtomicReference<>( );
+ final private AtomicReference
+
+ org.apache.archiva.redback
+ redback-authentication-jwt
+
org.apache.archiva.components.cache
archiva-components-spring-cache-api
-
- jakarta.servlet
- jakarta.servlet-api
+ org.apache.archiva.redback
+ redback-common-integrations
+
+ org.apache.archiva.redback
+ redback-rest-api
+
+
+
org.springframework
@@ -113,25 +122,47 @@
ehcache
+
+ jakarta.servlet
+ jakarta.servlet-api
+
+
+ jakarta.persistence
+ jakarta.persistence-api
+
+
+ jakarta.transaction
+ jakarta.transaction-api
+
+
+ jakarta.xml.bind
+ jakarta.xml.bind-api
+
+
+ jakarta.inject
+ jakarta.inject-api
+
+
+ jakarta.annotation
+ jakarta.annotation-api
+
+
+ jakarta.mail
+ jakarta.mail-api
+
+
+ jakarta.ws.rs
+ jakarta.ws.rs-api
+
+
+ com.sun.mail
+ jakarta.mail
+
org.apache.commons
commons-lang3
-
-
- org.apache.archiva.redback
- redback-rest-api
- ${project.version}
-
-
-
-
-
- org.apache.archiva.redback
- redback-common-integrations
-
-
commons-io
commons-io
@@ -148,6 +179,7 @@
com.fasterxml.jackson.datatype
jackson-datatype-jsr310
+ compile
org.apache.cxf
@@ -177,7 +209,6 @@
com.fasterxml.jackson.dataformat
jackson-dataformat-xml
- ${jackson.version}
@@ -192,6 +223,11 @@
runtime
+
+ org.apache.archiva.redback
+ redback-users-memory
+ test
+
org.apache.archiva.redback
redback-rbac-jpa
@@ -305,39 +341,19 @@
test
+
+ org.mockito
+ mockito-core
+ test
+
+
+ org.mockito
+ mockito-junit-jupiter
+ test
+
+
+
-
- jakarta.persistence
- jakarta.persistence-api
-
-
- jakarta.transaction
- jakarta.transaction-api
-
-
- jakarta.xml.bind
- jakarta.xml.bind-api
-
-
- jakarta.inject
- jakarta.inject-api
-
-
- jakarta.annotation
- jakarta.annotation-api
-
-
- jakarta.mail
- jakarta.mail-api
-
-
- jakarta.ws.rs
- jakarta.ws.rs-api
-
-
- com.sun.mail
- jakarta.mail
-
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/RedbackRequestInformation.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/RedbackRequestInformation.java
index 60bd5deb..1503a7ea 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/RedbackRequestInformation.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/RedbackRequestInformation.java
@@ -18,14 +18,19 @@
* under the License.
*/
+import org.apache.archiva.redback.system.SecuritySession;
import org.apache.archiva.redback.users.User;
/**
* @author Olivier Lamy
+ * @author Martin Stockhammer
* @since 1.4
+ *
*/
public class RedbackRequestInformation
{
+ private SecuritySession securitySession;
+
private User user;
private String remoteAddr;
@@ -36,6 +41,13 @@ public RedbackRequestInformation( User user, String remoteAddr )
this.remoteAddr = remoteAddr;
}
+ public RedbackRequestInformation( SecuritySession securitySession, User user, String remoteAddr )
+ {
+ this.securitySession = securitySession;
+ this.user = user;
+ this.remoteAddr = remoteAddr;
+ }
+
public User getUser()
{
return user;
@@ -55,4 +67,14 @@ public void setRemoteAddr( String remoteAddr )
{
this.remoteAddr = remoteAddr;
}
+
+ public SecuritySession getSecuritySession( )
+ {
+ return securitySession;
+ }
+
+ public void setSecuritySession( SecuritySession securitySession )
+ {
+ this.securitySession = securitySession;
+ }
}
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/AbstractInterceptor.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/AbstractInterceptor.java
index bc9aac4a..b8a220f4 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/AbstractInterceptor.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/AbstractInterceptor.java
@@ -25,6 +25,9 @@
import org.apache.archiva.redback.integration.filter.authentication.HttpAuthenticator;
import org.apache.archiva.redback.policy.AccountLockedException;
import org.apache.archiva.redback.policy.MustChangePasswordException;
+import org.apache.archiva.redback.rest.services.RedbackAuthenticationThreadLocal;
+import org.apache.archiva.redback.rest.services.RedbackRequestInformation;
+import org.apache.archiva.redback.system.SecuritySession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
@@ -50,6 +53,7 @@ public abstract class AbstractInterceptor
private Map authorizationCache = new HashMap<>( );
public static final String AUTHENTICATION_RESULT = "org.apache.archiva.authResult";
+ public static final String SECURITY_SESSION = "org.apache.archiva.securitySession";
@Context
private HttpServletRequest httpServletRequest;
@@ -67,6 +71,15 @@ public HttpServletResponse getHttpServletResponse( )
return httpServletResponse;
}
+ protected void setHttpServletRequest(HttpServletRequest request) {
+ this.httpServletRequest = request;
+ }
+
+ protected void setHttpServletResponse(HttpServletResponse response) {
+ this.httpServletResponse = response;
+ }
+
+
public RedbackAuthorization getRedbackAuthorization( ResourceInfo resourceInfo ) {
Method method = resourceInfo.getResourceMethod( );
RedbackAuthorization redbackAuthorization = getAuthorizationForMethod( method );
@@ -87,6 +100,21 @@ private RedbackAuthorization getAuthorizationForMethod(Method method) {
}
}
+ protected SecuritySession getSecuritySession(ContainerRequestContext containerRequestContext, HttpAuthenticator httpAuthenticator,
+ HttpServletRequest request) {
+ if ( containerRequestContext.getProperty( SECURITY_SESSION ) != null ) {
+ return (SecuritySession) containerRequestContext.getProperty( SECURITY_SESSION );
+ }
+ RedbackRequestInformation info = RedbackAuthenticationThreadLocal.get( );
+ SecuritySession securitySession = info == null ? null : info.getSecuritySession( );
+ if (securitySession!=null) {
+ return securitySession;
+ } else
+ {
+ return httpAuthenticator.getSecuritySession( request.getSession( true ) );
+ }
+ }
+
protected AuthenticationResult getAuthenticationResult( ContainerRequestContext containerRequestContext, HttpAuthenticator httpAuthenticator, HttpServletRequest request )
{
AuthenticationResult authenticationResult = null;
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/BearerAuthInterceptor.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/BearerAuthInterceptor.java
new file mode 100644
index 00000000..71d77ec9
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/BearerAuthInterceptor.java
@@ -0,0 +1,231 @@
+package org.apache.archiva.redback.rest.services.interceptors;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.archiva.redback.authentication.AuthenticationException;
+import org.apache.archiva.redback.authentication.AuthenticationFailureCause;
+import org.apache.archiva.redback.authentication.AuthenticationResult;
+import org.apache.archiva.redback.authentication.BearerTokenAuthenticationDataSource;
+import org.apache.archiva.redback.authentication.jwt.BearerError;
+import org.apache.archiva.redback.authentication.jwt.JwtAuthenticator;
+import org.apache.archiva.redback.authorization.RedbackAuthorization;
+import org.apache.archiva.redback.integration.filter.authentication.HttpAuthenticationException;
+import org.apache.archiva.redback.policy.AccountLockedException;
+import org.apache.archiva.redback.policy.MustChangePasswordException;
+import org.apache.archiva.redback.rest.services.RedbackAuthenticationThreadLocal;
+import org.apache.archiva.redback.rest.services.RedbackRequestInformation;
+import org.apache.archiva.redback.system.SecuritySession;
+import org.apache.archiva.redback.system.SecuritySystem;
+import org.apache.archiva.redback.users.User;
+import org.apache.archiva.redback.users.UserManager;
+import org.apache.archiva.redback.users.UserManagerException;
+import org.apache.archiva.redback.users.UserNotFoundException;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Priority;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.container.ResourceInfo;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.Provider;
+import java.io.IOException;
+import java.util.regex.Pattern;
+
+/**
+ * Interceptor that checks for the Bearer Header value and tries to verify the token.
+ *
+ * @author Martin Stockhammer
+ * @since 3.0
+ */
+@Service( "bearerAuthInterceptor#rest" )
+@Provider
+@Priority( Priorities.AUTHENTICATION )
+public class BearerAuthInterceptor extends AbstractInterceptor
+ implements ContainerRequestFilter
+{
+
+ private static final Logger log = LoggerFactory.getLogger( BearerAuthInterceptor.class );
+
+ @Inject
+ @Named( value = "userManager#default" )
+ private UserManager userManager;
+
+ @Inject
+ @Named( value = "securitySystem" )
+ SecuritySystem securitySystem;
+
+ @Inject
+ JwtAuthenticator jwtAuthenticator;
+
+ @Context
+ private ResourceInfo resourceInfo;
+
+ protected void setUserManager( UserManager userManager )
+ {
+ this.userManager = userManager;
+ }
+
+ protected void setJwtAuthenticator( JwtAuthenticator jwtAuthenticator )
+ {
+ this.jwtAuthenticator = jwtAuthenticator;
+ }
+
+ protected void setResourceInfo( ResourceInfo resourceInfo )
+ {
+ this.resourceInfo = resourceInfo;
+ }
+
+ @Override
+ public void filter( ContainerRequestContext requestContext ) throws IOException
+ {
+ log.debug( "Intercepting request for bearer token" );
+ // If no redback resource info, we deny the request
+ RedbackAuthorization redbackAuthorization = getRedbackAuthorization( resourceInfo );
+ if ( redbackAuthorization == null )
+ {
+ log.warn( "http path {} doesn't contain any informations regarding permissions ",
+ requestContext.getUriInfo( ).getRequestUri( ) );
+ // here we failed to authenticate so 403 as there is no detail on karma for this
+ // it must be marked as it's exposed
+ requestContext.abortWith( Response.status( Response.Status.FORBIDDEN ).build( ) );
+ return;
+ }
+ String bearerHeader = StringUtils.defaultIfEmpty( requestContext.getHeaderString( "Authorization" ), "" ).trim( );
+ if ( !"".equals( bearerHeader ) )
+ {
+ log.debug( "Found token" );
+ String bearerToken = bearerHeader.replaceFirst( "\\s*Bearer\\s+(\\S+)\\s*", "$1" );
+ final HttpServletRequest request = getHttpServletRequest( );
+ BearerTokenAuthenticationDataSource source = new BearerTokenAuthenticationDataSource( "", bearerToken );
+
+ if ( redbackAuthorization.noRestriction( ) )
+ {
+ log.debug( "No restriction for method {}#{}", resourceInfo.getResourceClass( ), resourceInfo.getResourceMethod( ) );
+ // maybe session exists so put it in threadLocal
+ // some services need the current user if logged
+ // maybe there is some authz in the request so try it but not fail so catch Exception !
+ try
+ {
+ SecuritySession securitySession = securitySystem.authenticate( source );
+ AuthenticationResult authenticationResult = securitySession.getAuthenticationResult( );
+
+ if ( ( authenticationResult == null ) || ( !authenticationResult.isAuthenticated( ) ) )
+ {
+ return;
+ }
+
+ User user = authenticationResult.getUser( ) == null ? userManager.findUser(
+ authenticationResult.getPrincipal( ) ) : authenticationResult.getUser( );
+ RedbackRequestInformation redbackRequestInformation =
+ new RedbackRequestInformation( securitySession, user, request.getRemoteAddr( ) );
+
+ RedbackAuthenticationThreadLocal.set( redbackRequestInformation );
+ // message.put( AuthenticationResult.class, authenticationResult );
+ requestContext.setProperty( AUTHENTICATION_RESULT, authenticationResult );
+ requestContext.setProperty( SECURITY_SESSION, securitySession );
+ }
+ catch ( Exception e )
+ {
+ log.debug( "Authentication failed {}", e.getMessage( ), e );
+ // ignore here
+ }
+ return;
+ }
+ HttpServletResponse response = getHttpServletResponse( );
+ try
+ {
+ SecuritySession securitySession = securitySystem.authenticate( source );
+ AuthenticationResult authenticationResult = securitySession.getAuthenticationResult( );
+
+ if ( ( authenticationResult == null ) || ( !authenticationResult.isAuthenticated( ) ) )
+ {
+ String error;
+ String message;
+ if ( authenticationResult.getAuthenticationFailureCauses( ).size( ) > 0 )
+ {
+ AuthenticationFailureCause cause = authenticationResult.getAuthenticationFailureCauses( ).get( 0 );
+ error = BearerError.get( cause.getCause( ) ).getError( );
+ message = cause.getMessage( );
+ }
+ else
+ {
+ error = "invalid_token";
+ message = "Unknown error";
+ }
+ response.setHeader( "WWW-Authenticate", "Bearer realm=\"" + request.getRemoteHost( ) + "\",error=\""
+ + error + "\",error_description=\"" + message + "\"" );
+ requestContext.abortWith( Response.status( Response.Status.UNAUTHORIZED ).build( ) );
+ return;
+ }
+
+ User user = authenticationResult.getUser( ) == null
+ ? userManager.findUser( authenticationResult.getPrincipal( ) )
+ : authenticationResult.getUser( );
+
+ RedbackRequestInformation redbackRequestInformation =
+ new RedbackRequestInformation( user, request.getRemoteAddr( ) );
+ redbackRequestInformation.setSecuritySession( securitySession );
+ RedbackAuthenticationThreadLocal.set( redbackRequestInformation );
+ // message.put( AuthenticationResult.class, authenticationResult );
+ requestContext.setProperty( AUTHENTICATION_RESULT, authenticationResult );
+ requestContext.setProperty( SECURITY_SESSION, securitySession );
+ return;
+ }
+ catch ( AuthenticationException e )
+ {
+ response.setHeader( "WWW-Authenticate", "Bearer realm=\"" + request.getRemoteHost( )
+ + "\",error=\"invalid_token\",error_description=\"" + e.getMessage( ) + "\"" );
+ requestContext.abortWith( Response.status( Response.Status.UNAUTHORIZED ).build( ) );
+ }
+ catch ( UserNotFoundException e )
+ {
+ response.setHeader( "WWW-Authenticate", "Bearer realm=\"" + request.getRemoteHost( )
+ + "\",error=\"invalid_token\",error_description=\"user not found\"" );
+ requestContext.abortWith( Response.status( Response.Status.UNAUTHORIZED ).build( ) );
+ }
+ catch ( UserManagerException e )
+ {
+ log.error( "Error from user manager " + e.getMessage( ) );
+ requestContext.abortWith( Response.status( Response.Status.INTERNAL_SERVER_ERROR ).build( ) );
+ }
+ catch ( AccountLockedException e )
+ {
+ response.setHeader( "WWW-Authenticate", "Bearer realm=\"" + request.getRemoteHost( )
+ + "\",error=\"invalid_token\",error_description=\"account locked\"" );
+ requestContext.abortWith( Response.status( Response.Status.UNAUTHORIZED ).build( ) );
+ }
+ catch ( MustChangePasswordException e )
+ {
+ response.setHeader( "WWW-Authenticate", "Bearer realm=\"" + request.getRemoteHost( )
+ + "\",error=\"invalid_token\",error_description=\"password change required\"" );
+ requestContext.abortWith( Response.status( Response.Status.UNAUTHORIZED ).build( ) );
+ }
+
+
+ }
+ }
+}
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/JacksonJsonConfigurator.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/JacksonJsonConfigurator.java
index 2551c5a6..b5d54a88 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/JacksonJsonConfigurator.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/JacksonJsonConfigurator.java
@@ -50,6 +50,7 @@ public JacksonJsonConfigurator( @Named("redbackJacksonJsonMapper") ObjectMapper
log.info( "configure jackson ObjectMapper" );
objectMapper.disable( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES );
objectMapper.setAnnotationIntrospector( new JaxbAnnotationIntrospector( objectMapper.getTypeFactory() ) );
+ objectMapper.findAndRegisterModules( );
objectMapper.registerModule( new JavaTimeModule( ) );
objectMapper.setDateFormat( new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSSZ" ) );
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/PermissionsInterceptor.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/PermissionsInterceptor.java
index 2461f594..64d11d08 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/PermissionsInterceptor.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/PermissionsInterceptor.java
@@ -24,13 +24,17 @@
import org.apache.archiva.redback.authorization.AuthorizationResult;
import org.apache.archiva.redback.authorization.RedbackAuthorization;
import org.apache.archiva.redback.integration.filter.authentication.basic.HttpBasicAuthentication;
+import org.apache.archiva.redback.rest.services.RedbackAuthenticationThreadLocal;
+import org.apache.archiva.redback.rest.services.RedbackRequestInformation;
import org.apache.archiva.redback.system.SecuritySession;
import org.apache.archiva.redback.system.SecuritySystem;
+import org.apache.archiva.redback.users.User;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
+import javax.annotation.Priority;
import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.http.HttpServletRequest;
@@ -48,6 +52,7 @@
*/
@Service( "permissionInterceptor#rest" )
@Provider
+@Priority( Priorities.AUTHORIZATION )
public class PermissionsInterceptor
extends AbstractInterceptor
implements ContainerRequestFilter
@@ -68,7 +73,7 @@ public class PermissionsInterceptor
public void filter( ContainerRequestContext containerRequestContext )
{
-
+ log.debug( "Filtering request" );
RedbackAuthorization redbackAuthorization = getRedbackAuthorization( resourceInfo );
if ( redbackAuthorization != null )
@@ -85,14 +90,14 @@ public void filter( ContainerRequestContext containerRequestContext )
&& !( permissions.length == 1 && StringUtils.isEmpty( permissions[0] ) ) )
{
HttpServletRequest request = getHttpServletRequest( );
- SecuritySession securitySession = httpAuthenticator.getSecuritySession( request.getSession() );
+ SecuritySession securitySession = getSecuritySession( containerRequestContext, httpAuthenticator, request );
AuthenticationResult authenticationResult = getAuthenticationResult( containerRequestContext, httpAuthenticator, request );
-
log.debug( "authenticationResult from message: {}", authenticationResult );
if ( authenticationResult != null && authenticationResult.isAuthenticated() )
{
+ User userObject = securitySession == null ? authenticationResult.getUser( ) : securitySession.getUser( );
for ( String permission : permissions )
{
log.debug( "check permission: {} with securitySession {}", permission, securitySession );
@@ -108,10 +113,13 @@ public void filter( ContainerRequestContext containerRequestContext )
log.debug("Found resource from annotated parameter: {}",resource);
}
- AuthorizationResult authorizationResult =
- securitySystem.authorize( authenticationResult.getUser(), permission, //
- StringUtils.isBlank( resource ) //
- ? null : resource );
+ AuthorizationResult authorizationResult = null;
+ if (userObject!=null)
+ {
+ authorizationResult = securitySystem.authorize( userObject, permission, //
+ StringUtils.isBlank( resource ) //
+ ? null : resource );
+ }
if ( authenticationResult != null && authorizationResult.isAuthorized() )
{
log.debug( "isAuthorized for permission {}", permission );
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/Priorities.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/Priorities.java
new file mode 100644
index 00000000..71e9fcce
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/Priorities.java
@@ -0,0 +1,30 @@
+package org.apache.archiva.redback.rest.services.interceptors;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+/**
+ * @author Martin Stockhammer
+ */
+public final class Priorities
+{
+ public static final int INITIALIZE = 100;
+ public static final int PRECHECK = 1000;
+ public static final int AUTHENTICATION = 2000;
+ public static final int AUTHORIZATION = 3000;
+}
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java
index da2a44dc..c134d172 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java
@@ -34,6 +34,7 @@
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
+import javax.annotation.Priority;
import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.http.HttpServletRequest;
@@ -64,6 +65,7 @@
*/
@Provider
@Service( "requestValidationInterceptor#rest" )
+@Priority( Priorities.PRECHECK )
public class RequestValidationInterceptor
extends AbstractInterceptor
implements ContainerRequestFilter
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/ThreadLocalUserCleaner.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/ThreadLocalUserCleaner.java
index dbce28f6..f43c5459 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/ThreadLocalUserCleaner.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/ThreadLocalUserCleaner.java
@@ -24,6 +24,7 @@
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
+import javax.annotation.Priority;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ContainerResponseContext;
@@ -39,6 +40,7 @@
@Service( "threadLocalUserCleaner#rest" )
@Provider
@PreMatching
+@Priority( Priorities.INITIALIZE )
public class ThreadLocalUserCleaner
implements ContainerResponseFilter
{
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultAuthenticationService.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultAuthenticationService.java
index 9537c3c3..c7c2af3d 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultAuthenticationService.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultAuthenticationService.java
@@ -22,8 +22,8 @@
import org.apache.archiva.redback.authentication.AuthenticationConstants;
import org.apache.archiva.redback.authentication.AuthenticationException;
import org.apache.archiva.redback.authentication.AuthenticationFailureCause;
-import org.apache.archiva.redback.authentication.EncryptionFailedException;
import org.apache.archiva.redback.authentication.PasswordBasedAuthenticationDataSource;
+import org.apache.archiva.redback.authentication.jwt.JwtAuthenticator;
import org.apache.archiva.redback.integration.filter.authentication.HttpAuthenticator;
import org.apache.archiva.redback.keys.AuthenticationKey;
import org.apache.archiva.redback.keys.KeyManager;
@@ -52,12 +52,12 @@
import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import java.time.Duration;
import java.time.Instant;
-import java.time.temporal.TemporalAmount;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
@@ -87,6 +87,12 @@ public class DefaultAuthenticationService
@Context
private HttpServletRequest httpServletRequest;
+ @Context
+ private HttpServletResponse response;
+
+ @Inject
+ private JwtAuthenticator jwtAuthenticator;
+
// validation token lifetime: 3 hours
long tokenLifetime = 1000*3600*3;
@@ -101,7 +107,6 @@ public DefaultAuthenticationService( SecuritySystem securitySystem,
@Override
public Token requestOnetimeToken( String providedKey, String principal, String purpose, int expirationSeconds )
- throws RedbackServiceException
{
KeyManager keyManager = securitySystem.getKeyManager();
AuthenticationKey key;
@@ -133,18 +138,20 @@ public Token requestOnetimeToken( String providedKey, String principal, String p
@Override
public PingResult ping()
- throws RedbackServiceException
{
return new PingResult( true);
}
@Override
public PingResult pingWithAutz()
- throws RedbackServiceException
{
return new PingResult( true );
}
+ private Token getRestToken( org.apache.archiva.redback.authentication.Token token ) {
+ return Token.of( token.getData( ), token.getMetadata( ).created( ), token.getMetadata( ).validBefore( ), token.getMetadata( ).getUser( ), "rest-auth" );
+ }
+
@Override
public Token logIn( LoginRequest loginRequest )
throws RedbackServiceException
@@ -157,32 +164,22 @@ public Token logIn( LoginRequest loginRequest )
{
SecuritySession securitySession = securitySystem.authenticate( authDataSource );
log.debug("Security session {}", securitySession);
- if ( securitySession.getAuthenticationResult().isAuthenticated() )
+ if ( securitySession.getAuthenticationResult() != null
+ && securitySession.getAuthenticationResult().isAuthenticated() )
{
org.apache.archiva.redback.users.User user = securitySession.getUser();
- log.debug("user {} authenticated", user.getUsername());
+ org.apache.archiva.redback.authentication.Token token = jwtAuthenticator.generateToken( user.getUsername( ) );
+ log.debug("User {} authenticated", user.getUsername());
if ( !user.isValidated() )
{
log.info( "user {} not validated", user.getUsername() );
- return null;
+ throw new RedbackServiceException( "redback:user-not-validated", Response.Status.FORBIDDEN.getStatusCode() );
}
- UserLogin restUser = buildRestUser( user );
- restUser.setReadOnly( securitySystem.userManagerReadOnly() );
- // validationToken only set during login
- try {
- String validationToken = securitySystem.getTokenManager().encryptToken(user.getUsername(), tokenLifetime);
- restUser.setValidationToken(validationToken);
- log.debug("Validation Token set {}",validationToken);
-
- } catch (EncryptionFailedException e) {
- log.error("Validation token could not be created "+e.getMessage());
- }
-
- // here create an http session
- httpAuthenticator.authenticate( authDataSource, httpServletRequest.getSession( true ) );
- return null;
- }
- if ( securitySession.getAuthenticationResult() != null
+ // Stateless services no session
+ // httpAuthenticator.authenticate( authDataSource, httpServletRequest.getSession( true ) );
+ Token restToken = getRestToken( token );
+ return restToken;
+ } else if ( securitySession.getAuthenticationResult() != null
&& securitySession.getAuthenticationResult().getAuthenticationFailureCauses() != null )
{
List errorMessages = new ArrayList();
@@ -190,33 +187,42 @@ public Token logIn( LoginRequest loginRequest )
{
if ( authenticationFailureCause.getCause() == AuthenticationConstants.AUTHN_NO_SUCH_USER )
{
- errorMessages.add( new ErrorMessage( "incorrect.username.password" ) );
+ errorMessages.add( new ErrorMessage( "redback:incorrect.username.password" ) );
}
else
{
- errorMessages.add( new ErrorMessage().message( authenticationFailureCause.getMessage() ) );
+ errorMessages.add( new ErrorMessage().message( "redback:"+authenticationFailureCause.getMessage() ) );
}
}
-
- throw new RedbackServiceException( errorMessages );
+ response.setHeader( "WWW-Authenticate", "redback-login realm="+httpServletRequest.getRemoteHost() );
+ throw new RedbackServiceException( errorMessages , Response.Status.UNAUTHORIZED.getStatusCode());
}
- return null;
+ response.setHeader( "WWW-Authenticate", "redback-login realm="+httpServletRequest.getRemoteHost() );
+ throw new RedbackServiceException( "redback:login-failed", Response.Status.UNAUTHORIZED.getStatusCode() );
}
+
catch ( AuthenticationException e )
{
- throw new RedbackServiceException( e.getMessage(), Response.Status.FORBIDDEN.getStatusCode() );
+ log.debug( "Authentication error: {}", e.getMessage( ), e );
+ throw new RedbackServiceException( "redback:login-failed", Response.Status.UNAUTHORIZED.getStatusCode() );
}
- catch ( UserNotFoundException | AccountLockedException e )
+ catch ( UserNotFoundException e )
{
- throw new RedbackServiceException( e.getMessage() );
+ log.debug( "User not found: {}", e.getMessage( ), e );
+ throw new RedbackServiceException( "redback:login-failed", Response.Status.UNAUTHORIZED.getStatusCode() );
+ }
+ catch (AccountLockedException e) {
+ log.info( "Account locked: {}", e.getMessage( ), e );
+ throw new RedbackServiceException( "redback:account-locked", Response.Status.FORBIDDEN.getStatusCode() );
}
catch ( MustChangePasswordException e )
{
- return null;
+ log.debug( "Password change required: {}", e.getMessage( ), e );
+ throw new RedbackServiceException( "redback:password-change-required", Response.Status.FORBIDDEN.getStatusCode( ) );
}
catch ( UserManagerException e )
{
- log.info( "UserManagerException: {}", e.getMessage() );
+ log.warn( "UserManagerException: {}", e.getMessage() );
List errorMessages =
Arrays.asList( new ErrorMessage().message( "UserManagerException: " + e.getMessage() ) );
throw new RedbackServiceException( errorMessages );
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/resources/META-INF/spring-context.xml b/redback-integrations/redback-rest/redback-rest-services/src/main/resources/META-INF/spring-context.xml
index 2fd63b1a..2b51cb53 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/main/resources/META-INF/spring-context.xml
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/resources/META-INF/spring-context.xml
@@ -88,7 +88,7 @@
-
+
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/AbstractRestServicesTest.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/AbstractRestServicesTest.java
index fd3d8920..ff9a69f7 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/AbstractRestServicesTest.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/AbstractRestServicesTest.java
@@ -19,6 +19,8 @@
* under the License.
*/
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
import junit.framework.TestCase;
import org.apache.archiva.redback.integration.security.role.RedbackRoleConstants;
@@ -83,6 +85,14 @@ public int getServerPort() {
}
}
+ JacksonJaxbJsonProvider getJsonProvider() {
+ JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider( );
+ ObjectMapper mapper = new ObjectMapper( );
+ mapper.registerModule( new JavaTimeModule( ) );
+ provider.setMapper( mapper );
+ return provider;
+ }
+
/**
* Returns true, if the server does exist and is running.
* @return true, if server does exist and is running.
@@ -173,7 +183,7 @@ protected FakeCreateAdminService getFakeCreateAdminService()
{
return JAXRSClientFactory.create(
"http://localhost:" + getServerPort()+ "/" + getRestServicesPath() + "/fakeCreateAdminService/",
- FakeCreateAdminService.class, Collections.singletonList( new JacksonJaxbJsonProvider() ) );
+ FakeCreateAdminService.class, Collections.singletonList( getJsonProvider() ) );
}
@After
@@ -241,7 +251,7 @@ protected LoginService getLoginService( String authzHeader )
{
LoginService service =
JAXRSClientFactory.create( "http://localhost:" + getServerPort() + "/" + getRestServicesPath() + "/redbackServices/",
- LoginService.class, Collections.singletonList( new JacksonJaxbJsonProvider() ) );
+ LoginService.class, Collections.singletonList( getJsonProvider() ) );
// for debuging purpose
WebClient.getConfig( service ).getHttpConduit().getClient().setReceiveTimeout( getTimeout() );
@@ -262,7 +272,7 @@ protected AuthenticationService getLoginServiceV2( String authzHeader )
{
AuthenticationService service =
JAXRSClientFactory.create( "http://localhost:" + getServerPort() + "/" + getRestServicesPath() + "/v2/redback/",
- AuthenticationService.class, Collections.singletonList( new JacksonJaxbJsonProvider() ) );
+ AuthenticationService.class, Collections.singletonList( getJsonProvider() ) );
// for debuging purpose
WebClient.getConfig( service ).getHttpConduit().getClient().setReceiveTimeout( getTimeout() );
@@ -285,7 +295,7 @@ protected LdapGroupMappingService getLdapGroupMappingService( String authzHeader
LdapGroupMappingService service =
JAXRSClientFactory.create( "http://localhost:" + getServerPort() + "/" + getRestServicesPath() + "/redbackServices/",
LdapGroupMappingService.class,
- Collections.singletonList( new JacksonJaxbJsonProvider() ) );
+ Collections.singletonList( getJsonProvider() ) );
// for debuging purpose
WebClient.getConfig( service ).getHttpConduit().getClient().setReceiveTimeout( getTimeout() );
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/interceptors/BearerAuthInterceptorTest.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/interceptors/BearerAuthInterceptorTest.java
new file mode 100644
index 00000000..bc7519fc
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/interceptors/BearerAuthInterceptorTest.java
@@ -0,0 +1,151 @@
+package org.apache.archiva.redback.rest.services.interceptors;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.archiva.redback.authentication.AuthenticationException;
+import org.apache.archiva.redback.authentication.Token;
+import org.apache.archiva.redback.authentication.jwt.JwtAuthenticator;
+import org.apache.archiva.redback.configuration.UserConfiguration;
+import org.apache.archiva.redback.policy.AccountLockedException;
+import org.apache.archiva.redback.policy.MustChangePasswordException;
+import org.apache.archiva.redback.rest.services.RedbackAuthenticationThreadLocal;
+import org.apache.archiva.redback.rest.services.RedbackRequestInformation;
+import org.apache.archiva.redback.rest.services.v2.DefaultAuthenticationService;
+import org.apache.archiva.redback.system.DefaultSecuritySession;
+import org.apache.archiva.redback.system.SecuritySystem;
+import org.apache.archiva.redback.users.User;
+import org.apache.archiva.redback.users.UserManager;
+import org.apache.archiva.redback.users.UserManagerException;
+import org.apache.archiva.redback.users.memory.SimpleUser;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ResourceInfo;
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * @author Martin Stockhammer
+ */
+@ExtendWith( MockitoExtension.class )
+class BearerAuthInterceptorTest
+{
+
+ @Mock
+ UserConfiguration userConfiguration;
+
+ @Mock
+ UserManager userManager;
+
+ @Mock
+ private ResourceInfo resourceInfo;
+
+ @Mock
+ private HttpServletRequest httpServletRequest;
+
+ @Mock
+ private HttpServletResponse httpServletResponse;
+
+ @Mock
+ private SecuritySystem securitySystem;
+
+ private JwtAuthenticator jwtAuthenticator;
+
+ BearerAuthInterceptor interceptor;
+
+ @BeforeEach
+ void setup() throws AuthenticationException, UserManagerException, AccountLockedException, MustChangePasswordException
+ {
+ // when( userConfiguration.getString( anyString( ) ) ).thenReturn( null );
+ doAnswer( invocation -> invocation.getArgument( 1 ).toString() ).when( userConfiguration ).getString( anyString( ), anyString( ) );
+ doAnswer( invocation -> (int)invocation.getArgument( 1 ) ).when( userConfiguration ).getInt( anyString( ), anyInt() );
+
+ interceptor = new BearerAuthInterceptor( );
+ interceptor.setHttpServletRequest( httpServletRequest );
+ interceptor.setHttpServletResponse( httpServletResponse );
+ interceptor.setResourceInfo( resourceInfo );
+ interceptor.setUserManager( userManager );
+ interceptor.securitySystem = securitySystem;
+ this.jwtAuthenticator = new JwtAuthenticator( );
+ jwtAuthenticator.setUserConfiguration( userConfiguration );
+ jwtAuthenticator.init();
+ interceptor.setJwtAuthenticator( jwtAuthenticator );
+ doAnswer( invocation -> new DefaultSecuritySession( jwtAuthenticator.authenticate( invocation.getArgument( 0 ) )) )
+ .when( securitySystem ).authenticate( any( ) );
+
+ }
+
+ @Test
+ void filter() throws IOException, NoSuchMethodException, UserManagerException
+ {
+ Token token = jwtAuthenticator.generateToken( "gandalf" );
+ when( resourceInfo.getResourceMethod( ) ).thenReturn( DefaultAuthenticationService.class.getDeclaredMethod( "ping" ) );
+ doReturn( DefaultAuthenticationService.class ).when( resourceInfo ).getResourceClass( );
+ ContainerRequestContext context = mock( ContainerRequestContext.class );
+ when( context.getHeaderString( "Authorization" ) ).thenReturn( "Bearer " + token.getData( ) );
+ User user = new SimpleUser( );
+ user.setUsername( "gandalf" );
+ when( userManager.findUser( "gandalf" ) ).thenReturn( user );
+ interceptor.filter( context);
+ verify( context, never() ).abortWith( any() );
+ RedbackRequestInformation info = RedbackAuthenticationThreadLocal.get( );
+ assertNotNull( info );
+ assertEquals( "gandalf", info.getUser( ).getUsername( ) );
+ }
+
+
+ @Test
+ void filterWithInvalidToken() throws IOException, NoSuchMethodException
+ {
+ RedbackAuthenticationThreadLocal.set( null );
+ Token token = jwtAuthenticator.generateToken( "gandalf" );
+ when( resourceInfo.getResourceMethod( ) ).thenReturn( DefaultAuthenticationService.class.getDeclaredMethod( "pingWithAutz") );
+ doReturn( DefaultAuthenticationService.class ).when( resourceInfo ).getResourceClass( );
+ ContainerRequestContext context = mock( ContainerRequestContext.class );
+ when( context.getHeaderString( "Authorization" ) ).thenReturn( "Bearer xxxxx" );
+ interceptor.filter( context);
+ verify( context, times(1) ).abortWith( argThat( response -> response.getStatus() == 401 ) );
+ verify( httpServletResponse, times(1) ).setHeader( eq("WWW-Authenticate"), anyString( ) );
+ RedbackRequestInformation info = RedbackAuthenticationThreadLocal.get( );
+ assertNull( info );
+ }
+
+ @Test
+ void filterWithInvalidTokenUnrestrictedMethod() throws IOException, NoSuchMethodException
+ {
+ RedbackAuthenticationThreadLocal.set( null );
+ Token token = jwtAuthenticator.generateToken( "gandalf" );
+ when( resourceInfo.getResourceMethod( ) ).thenReturn( DefaultAuthenticationService.class.getDeclaredMethod( "ping") );
+ doReturn( DefaultAuthenticationService.class ).when( resourceInfo ).getResourceClass( );
+ ContainerRequestContext context = mock( ContainerRequestContext.class );
+ when( context.getHeaderString( "Authorization" ) ).thenReturn( "Bearer xxxxx" );
+ interceptor.filter( context);
+ RedbackRequestInformation info = RedbackAuthenticationThreadLocal.get( );
+ assertNull( info );
+ }
+
+}
\ No newline at end of file
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractRestServicesTestV2.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractRestServicesTestV2.java
new file mode 100644
index 00000000..ccc9abe8
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractRestServicesTestV2.java
@@ -0,0 +1,332 @@
+package org.apache.archiva.redback.rest.services.v2;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
+import org.apache.archiva.redback.authentication.Token;
+import org.apache.archiva.redback.authentication.jwt.JwtAuthenticator;
+import org.apache.archiva.redback.integration.security.role.RedbackRoleConstants;
+import org.apache.archiva.redback.rest.api.services.RoleManagementService;
+import org.apache.archiva.redback.rest.api.services.v2.AuthenticationService;
+import org.apache.archiva.redback.rest.services.FakeCreateAdminService;
+import org.apache.archiva.redback.rest.services.FakeCreateAdminServiceImpl;
+import org.apache.archiva.redback.role.RoleManager;
+import org.apache.archiva.redback.users.User;
+import org.apache.archiva.redback.users.UserManager;
+import org.apache.archiva.redback.users.UserManagerException;
+import org.apache.archiva.redback.users.UserNotFoundException;
+import org.apache.commons.lang3.SystemUtils;
+import org.apache.cxf.common.util.Base64Utility;
+import org.apache.cxf.jaxrs.client.JAXRSClientFactory;
+import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.cxf.transport.servlet.CXFServlet;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.web.context.ContextLoaderListener;
+
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingException;
+import javax.naming.directory.DirContext;
+import javax.ws.rs.core.MediaType;
+import java.util.Collections;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+/**
+ * @author Olivier Lamy
+ */
+@ExtendWith( SpringExtension.class )
+@ContextConfiguration( locations = { "classpath*:/META-INF/spring-context.xml", "classpath*:/spring-context.xml" } )
+public abstract class AbstractRestServicesTestV2
+{
+
+ private JwtAuthenticator jwtAuthenticator;
+ private UserManager userManager;
+
+ protected Logger log = LoggerFactory.getLogger( getClass() );
+
+ private static AtomicReference server = new AtomicReference<>();
+ private static AtomicReference serverConnector = new AtomicReference<>();
+ private RoleManager roleManager;
+
+ protected void init() {
+ }
+
+ protected void destroy() {
+ this.jwtAuthenticator = null;
+ this.userManager = null;
+ }
+
+ /**
+ * Returns the server that was started, or null if not initialized before.
+ * @return
+ */
+ public Server getServer() {
+ return this.server.get();
+ }
+
+ public int getServerPort() {
+ ServerConnector connector = serverConnector.get();
+ if (connector!=null) {
+ return connector.getLocalPort();
+ } else {
+ return 0;
+ }
+ }
+
+ public JwtAuthenticator getJwtAuthenticator() {
+ if (this.jwtAuthenticator == null) {
+ JwtAuthenticator auth = ContextLoaderListener.getCurrentWebApplicationContext( )
+ .getBean( JwtAuthenticator.class );
+ assertNotNull( auth );
+ this.jwtAuthenticator = auth;
+ }
+ return this.jwtAuthenticator;
+ }
+
+ public UserManager getUserManager() {
+ if (this.userManager==null) {
+ UserManager userManager = ContextLoaderListener.getCurrentWebApplicationContext( )
+ .getBean( "userManager#default", UserManager.class );
+ assertNotNull( userManager );
+ this.userManager = userManager;
+ }
+ return this.userManager;
+ }
+
+ public RoleManager getRoleManager() {
+ if (this.roleManager==null) {
+ RoleManager roleManager = ContextLoaderListener.getCurrentWebApplicationContext( )
+ .getBean( "roleManager", RoleManager.class );
+ assertNotNull( roleManager );
+ this.roleManager = roleManager;
+ }
+ return this.roleManager;
+ }
+
+ JacksonJaxbJsonProvider getJsonProvider() {
+ JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider( );
+ ObjectMapper mapper = new ObjectMapper( );
+ mapper.registerModule( new JavaTimeModule( ) );
+ provider.setMapper( mapper );
+ return provider;
+ }
+
+ protected boolean exists( DirContext context, String dn )
+ {
+ Object result = null;
+ try {
+ result = context.lookup( dn );
+ }
+ catch ( NameNotFoundException e ) {
+ return false;
+ }
+ catch ( NamingException e )
+ {
+ log.error( "Unknown error during lookup: {}", e.getMessage( ) );
+ }
+ return result != null;
+ }
+
+ protected void deleteUser(User user) {
+ if (user!=null)
+ {
+ deleteUser( user.getUsername( ) );
+ }
+ }
+
+ protected void deleteUser(String userName) {
+ if (userName!=null)
+ {
+ try
+ {
+ getUserManager( ).deleteUser( userName );
+ }
+ catch ( UserNotFoundException e )
+ {
+ // ignore
+ }
+ catch ( UserManagerException e )
+ {
+ log.error( "Could not delete user {}", userName );
+ }
+ }
+ }
+
+ /**
+ * Returns true, if the server does exist and is running.
+ * @return true, if server does exist and is running.
+ */
+ public boolean isServerRunning() {
+ return this.server.get() != null && this.server.get().isRunning();
+ }
+
+ /**
+ * Returns the timeout in ms for rest requests. The timeout can be set by
+ * the system property rest.test.timeout
.
+ * @return The timeout value in ms.
+ */
+ public long getTimeout()
+ {
+ return Long.getLong( "rest.test.timeout", 1000000 );
+ }
+
+ public static String encode( String uid, String password )
+ {
+ return "Basic " + Base64Utility.encode( ( uid + ":" + password ).getBytes() );
+ }
+
+ public String getAdminAuthzHeader()
+ {
+ assertNotNull( getJwtAuthenticator());
+ String adminUser = RedbackRoleConstants.ADMINISTRATOR_ACCOUNT_NAME;
+ Token token = getJwtAuthenticator().generateToken( adminUser );
+ return "Bearer " + token.getData( );
+ }
+
+ public String getAuthHeader(String userId) {
+ assertNotNull( getJwtAuthenticator() );
+ Token token = getJwtAuthenticator().generateToken( userId );
+ return "Bearer " + token.getData( );
+ }
+
+ protected String getSpringConfigLocation()
+ {
+ return "classpath*:spring-context.xml,classpath*:META-INF/spring-context.xml";
+ }
+
+
+ protected String getRestServicesPath()
+ {
+ return "restServices";
+ }
+
+ public void startServer()
+ throws Exception
+ {
+ log.info("Starting server");
+ Server myServer = new Server();
+ this.server.set(myServer);
+ this.serverConnector.set(new ServerConnector( myServer, new HttpConnectionFactory()));
+ myServer.addConnector(serverConnector.get());
+
+ ServletHolder servletHolder = new ServletHolder( new CXFServlet() );
+ ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
+ context.setResourceBase( SystemUtils.JAVA_IO_TMPDIR );
+ context.setSessionHandler( new SessionHandler( ) );
+ context.addServlet( servletHolder, "/" + getRestServicesPath() + "/*" );
+ context.setInitParameter( "contextConfigLocation", getSpringConfigLocation() );
+ context.addEventListener(new ContextLoaderListener());
+
+ getServer().setHandler( context );
+ getServer().start();
+
+ if (log.isDebugEnabled())
+ {
+ log.debug( "Jetty dump: {}", getServer().dump() );
+ }
+
+ log.info( "Started server on port {}", getServerPort() );
+
+ UserManager um = getUserManager( );
+
+ User adminUser = null;
+ try
+ {
+ adminUser = um.findUser( RedbackRoleConstants.ADMINISTRATOR_ACCOUNT_NAME );
+ } catch ( UserNotFoundException e ) {
+ // ignore
+ }
+ if (adminUser==null)
+ {
+ adminUser = um.createUser( RedbackRoleConstants.ADMINISTRATOR_ACCOUNT_NAME, "Administrator", "admin@local.home" );
+ adminUser.setUsername( RedbackRoleConstants.ADMINISTRATOR_ACCOUNT_NAME );
+ adminUser.setPassword( FakeCreateAdminServiceImpl.ADMIN_TEST_PWD );
+ adminUser.setFullName( "the admin user" );
+ adminUser.setEmail( "toto@toto.fr" );
+ adminUser.setPermanent( true );
+ adminUser.setValidated( true );
+ adminUser.setLocked( false );
+ adminUser.setPasswordChangeRequired( false );
+ um.addUser( adminUser );
+
+ getRoleManager( ).assignRole( "system-administrator", adminUser.getUsername( ) );
+ }
+
+ FakeCreateAdminService fakeCreateAdminService = getFakeCreateAdminService();
+ this.jwtAuthenticator = null;
+
+ //assertTrue( res.booleanValue() );
+
+ }
+
+ protected FakeCreateAdminService getFakeCreateAdminService()
+ {
+ return JAXRSClientFactory.create(
+ "http://localhost:" + getServerPort()+ "/" + getRestServicesPath() + "/fakeCreateAdminService/",
+ FakeCreateAdminService.class, Collections.singletonList( getJsonProvider() ) );
+ }
+
+ public void stopServer()
+ throws Exception
+ {
+ if ( getServer() != null )
+ {
+ log.info("Stopping server");
+ getServer().stop();
+ }
+ }
+
+ protected AuthenticationService getLoginServiceV2( String authzHeader )
+ {
+ AuthenticationService service =
+ JAXRSClientFactory.create( "http://localhost:" + getServerPort() + "/" + getRestServicesPath() + "/v2/redback/",
+ AuthenticationService.class, Collections.singletonList( getJsonProvider() ) );
+
+ // for debuging purpose
+ WebClient.getConfig( service ).getHttpConduit().getClient().setReceiveTimeout( getTimeout() );
+
+ if ( authzHeader != null )
+ {
+ WebClient.client( service ).header( "Authorization", authzHeader );
+ }
+ WebClient.client(service).header("Referer","http://localhost:"+getServerPort());
+
+ WebClient.client( service ).accept( MediaType.APPLICATION_JSON_TYPE );
+ WebClient.client( service ).type( MediaType.APPLICATION_JSON_TYPE );
+
+ return service;
+ }
+
+
+
+}
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AuthenticationServiceTest.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AuthenticationServiceTest.java
index b494e592..9069a032 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AuthenticationServiceTest.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AuthenticationServiceTest.java
@@ -21,30 +21,50 @@
import org.apache.archiva.redback.integration.security.role.RedbackRoleConstants;
import org.apache.archiva.redback.rest.api.model.LoginRequest;
import org.apache.archiva.redback.rest.api.model.Token;
-import org.apache.archiva.redback.rest.api.model.User;
import org.apache.archiva.redback.rest.api.services.RedbackServiceException;
import org.apache.archiva.redback.rest.api.services.UserService;
-import org.apache.archiva.redback.rest.services.AbstractRestServicesTest;
import org.apache.archiva.redback.rest.services.FakeCreateAdminService;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import org.apache.archiva.redback.users.User;
+import org.apache.archiva.redback.users.UserManager;
+import org.apache.archiva.redback.users.UserManagerException;
+import org.apache.archiva.redback.users.memory.SimpleUser;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.test.context.ContextConfiguration;
-import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import static org.junit.jupiter.api.Assertions.*;
/**
* @author Olivier Lamy
*/
-@RunWith( SpringJUnit4ClassRunner.class )
+@ExtendWith( SpringExtension.class )
@ContextConfiguration(
locations = { "classpath:/spring-context.xml" } )
public class AuthenticationServiceTest
- extends AbstractRestServicesTest
+ extends AbstractRestServicesTestV2
{
+ @BeforeEach
+ void setup() throws Exception
+ {
+ super.init();
+ super.startServer();
+ }
+
+ @AfterEach
+ void stop() throws Exception
+ {
+ super.stopServer();
+ super.destroy();
+ }
+
@Test
public void loginAdmin()
throws Exception
{
- assertNotNull( getLoginService( null ).logIn( new LoginRequest( RedbackRoleConstants.ADMINISTRATOR_ACCOUNT_NAME,
+ assertNotNull( getLoginServiceV2( null ).logIn( new LoginRequest( RedbackRoleConstants.ADMINISTRATOR_ACCOUNT_NAME,
FakeCreateAdminService.ADMIN_TEST_PWD ) ) );
}
@@ -56,44 +76,46 @@ public void createUserThenLog()
{
// START SNIPPET: create-user
- User user = new User( "toto", "toto the king", "toto@toto.fr", false, false );
+ UserManager um = getUserManager( );
+ User user = um.createUser( "toto", "toto the king", "toto@toto.fr" );
+ user.setValidated( true );
+ user.setLocked( false );
user.setPassword( "foo123" );
user.setPermanent( false );
user.setPasswordChangeRequired( false );
user.setLocked( false );
- user.setValidated( true );
- UserService userService = getUserService( authorizationHeader );
- userService.createUser( user );
+ user = um.addUser( user );
// END SNIPPET: create-user
- user = userService.getUser( "toto" );
assertNotNull( user );
assertEquals( "toto the king", user.getFullName() );
assertEquals( "toto@toto.fr", user.getEmail() );
- getLoginServiceV2( encode( "toto", "foo123" ) ).pingWithAutz();
+ getLoginServiceV2( getAuthHeader( "toto" ) ).pingWithAutz();
}
finally
{
- getUserService( authorizationHeader ).deleteUser( "toto" );
- getUserService( authorizationHeader ).removeFromCache( "toto" );
- assertNull( getUserService( authorizationHeader ).getUser( "toto" ) );
+ deleteUser( "toto" );
}
}
@Test
- public void simpleLogin() throws RedbackServiceException
+ public void simpleLogin() throws RedbackServiceException, UserManagerException
{
+ String authorizationHeader = getAdminAuthzHeader( );
try
{
// START SNIPPET: create-user
- User user = new User( "toto", "toto the king", "toto@toto.fr", false, false );
+ UserManager um = getUserManager( );
+ User user = um.createUser( "toto", "toto the king", "toto@toto.fr" );
user.setPassword( "foo123" );
user.setPermanent( false );
user.setPasswordChangeRequired( false );
user.setLocked( false );
user.setValidated( true );
- UserService userService = getUserService( authorizationHeader );
- userService.createUser( user );
+ user = um.addUser( user );
+ // We need this additional round, because new users have the password change flag set to true
+ user.setPasswordChangeRequired( false );
+ um.updateUser( user );
// END SNIPPET: create-user
LoginRequest request = new LoginRequest( "toto", "foo123" );
Token result = getLoginServiceV2( "" ).logIn( request );
@@ -103,9 +125,7 @@ public void simpleLogin() throws RedbackServiceException
}
finally
{
- getUserService( authorizationHeader ).deleteUser( "toto" );
- getUserService( authorizationHeader ).removeFromCache( "toto" );
- assertNull( getUserService( authorizationHeader ).getUser( "toto" ) );
+ deleteUser( "toto" );
}
}
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/GroupServiceTest.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/GroupServiceTest.java
index 7fcdc181..70e412d1 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/GroupServiceTest.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/GroupServiceTest.java
@@ -22,19 +22,23 @@
import org.apache.archiva.components.apacheds.ApacheDs;
import org.apache.archiva.redback.rest.api.model.GroupMapping;
import org.apache.archiva.redback.rest.api.services.v2.GroupService;
-import org.apache.archiva.redback.rest.services.AbstractRestServicesTest;
import org.apache.commons.lang3.StringUtils;
import org.apache.cxf.jaxrs.client.JAXRSClientFactory;
import org.apache.cxf.jaxrs.client.WebClient;
import org.assertj.core.api.Condition;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.springframework.test.annotation.DirtiesContext;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.test.context.ContextConfiguration;
-import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
import javax.inject.Inject;
import javax.inject.Named;
+import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
@@ -51,12 +55,12 @@
/**
* @author Olivier Lamy
*/
-@RunWith( SpringJUnit4ClassRunner.class )
+@ExtendWith( SpringExtension.class )
@ContextConfiguration(
- locations = { "classpath:/ldap-spring-test.xml" } )
-@DirtiesContext( classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD )
+ locations = {"classpath:/ldap-spring-test.xml"} )
+@TestInstance( TestInstance.Lifecycle.PER_CLASS )
public class GroupServiceTest
- extends AbstractRestServicesTest
+ extends AbstractRestServicesTestV2
{
@Inject
@@ -73,18 +77,18 @@ public class GroupServiceTest
protected GroupService getGroupService( String authzHeader )
{
GroupService service =
- JAXRSClientFactory.create( "http://localhost:" + getServerPort() + "/" + getRestServicesPath() + "/v2/redback/",
+ JAXRSClientFactory.create( "http://localhost:" + getServerPort( ) + "/" + getRestServicesPath( ) + "/v2/redback/",
GroupService.class,
- Collections.singletonList( new JacksonJaxbJsonProvider() ) );
+ Collections.singletonList( new JacksonJaxbJsonProvider( ) ) );
// for debuging purpose
- WebClient.getConfig( service ).getHttpConduit().getClient().setReceiveTimeout( getTimeout() );
+ WebClient.getConfig( service ).getHttpConduit( ).getClient( ).setReceiveTimeout( getTimeout( ) );
if ( authzHeader != null )
{
WebClient.client( service ).header( "Authorization", authzHeader );
}
- WebClient.client(service).header("Referer","http://localhost:"+getServerPort());
+ WebClient.client( service ).header( "Referer", "http://localhost:" + getServerPort( ) );
WebClient.client( service ).accept( MediaType.APPLICATION_JSON_TYPE );
WebClient.client( service ).type( MediaType.APPLICATION_JSON_TYPE );
@@ -93,70 +97,158 @@ protected GroupService getGroupService( String authzHeader )
}
@Override
- protected String getSpringConfigLocation()
+ protected String getSpringConfigLocation( )
{
return "classpath*:spring-context.xml,classpath*:META-INF/spring-context.xml,classpath:/ldap-spring-test.xml";
}
- @Override
- public void startServer()
+ @BeforeAll
+ public void startup( )
throws Exception
{
- super.startServer();
-
- groupSuffix = apacheDs.addSimplePartition( "test", new String[]{ "archiva", "apache", "org" } ).getSuffix();
-
- log.info( "groupSuffix: {}", groupSuffix );
+ super.init( );
+ super.startServer( );
suffix = "ou=People,dc=archiva,dc=apache,dc=org";
-
log.info( "DN Suffix: {}", suffix );
-
- apacheDs.startServer();
-
- BasicAttribute objectClass = new BasicAttribute( "objectClass" );
- objectClass.add( "top" );
- objectClass.add( "organizationalUnit" );
-
- Attributes attributes = new BasicAttributes( true );
- attributes.put( objectClass );
- attributes.put( "organizationalUnitName", "foo" );
-
- apacheDs.getAdminContext().createSubcontext( suffix, attributes );
-
- createGroups();
- }
-
- @Override
- public void stopServer()
- throws Exception
- {
-
- // cleanup ldap entries
- InitialDirContext context = apacheDs.getAdminContext();
-
- for ( String group : this.groups )
+ if ( apacheDs.isStopped( ) )
{
- context.unbind( createGroupDn( group ) );
+ groupSuffix = apacheDs.addSimplePartition( "test", new String[]{"archiva", "apache", "org"} ).getSuffix( );
+
+ log.info( "groupSuffix: {}", groupSuffix );
+ apacheDs.startServer( );
+ if ( !exists( apacheDs.getAdminContext( ), suffix ) )
+ {
+ BasicAttribute objectClass = new BasicAttribute( "objectClass" );
+ objectClass.add( "top" );
+ objectClass.add( "organizationalUnit" );
+
+ Attributes attributes = new BasicAttributes( true );
+ attributes.put( objectClass );
+ attributes.put( "organizationalUnitName", "foo" );
+
+ apacheDs.getAdminContext( ).createSubcontext( suffix, attributes );
+ }
}
-
- context.unbind( suffix );
-
- context.close();
-
- apacheDs.stopServer();
-
- super.stopServer();
}
- private void createGroups()
+ @BeforeEach
+ public void initLdap( ) throws Exception
+ {
+ removeAllGroups( );
+ createGroups( );
+ }
+
+ @AfterEach
+ public void cleanupLdap( ) throws NamingException
+ {
+ removeAllGroups( );
+ }
+
+ private void removeAllGroups( )
+ {
+ if (!apacheDs.isStopped())
+ {
+ InitialDirContext context = null;
+ try
+ {
+ context = apacheDs.getAdminContext( );
+ for ( String group : this.groups )
+ {
+ try
+ {
+ context.unbind( createGroupDn( group ) );
+ }
+ catch ( NamingException e )
+ {
+ // Ignore
+ }
+ }
+
+ }
+ catch ( NamingException e )
+ {
+ log.error( "Could not remove groups {}", e.getMessage( ), e );
+ }
+ finally
+ {
+ try
+ {
+ if ( context != null ) context.close( );
+ }
+ catch ( Exception e )
+ {
+ log.error( "Error during context close {}", e.getMessage( ) );
+ }
+ }
+ }
+ }
+
+ @AfterAll
+ public void stop( ) throws Exception
+
+ {
+
+ removeAllGroups( );
+ // cleanup ldap entries
+ try
+ {
+ InitialDirContext context = null;
+ try
+ {
+ context = apacheDs.getAdminContext( );
+ context.unbind( suffix );
+ }
+ finally
+ {
+ try
+ {
+ if ( context != null ) context.close( );
+ }
+ catch ( Exception e )
+ {
+ log.error( "Error during context close {}", e.getMessage( ) );
+ }
+ try
+ {
+ apacheDs.stopServer( );
+ }
+ catch ( Exception e )
+ {
+ log.error( "Could not stop apacheds {}", e.getMessage( ) );
+ }
+ }
+ }
+ catch ( Exception e )
+ {
+ log.error( "Could not stop ldap {}", e.getMessage( ) );
+ }
+ finally
+ {
+ super.stopServer( );
+ super.destroy( );
+ }
+ }
+
+ private void createGroups( )
throws Exception
{
- InitialDirContext context = apacheDs.getAdminContext();
-
- for ( String group : groups )
+ InitialDirContext context = null;
+ try
{
- createGroup( context, group, createGroupDn( group ) );
+ context = apacheDs.getAdminContext( );
+
+ for ( String group : groups )
+ {
+ createGroup( context, group, createGroupDn( group ) );
+ }
+ }
+ finally
+ {
+ if ( context != null )
+ {
+ context.close( );
+ }
}
}
@@ -164,19 +256,26 @@ private void createGroups()
private void createGroup( DirContext context, String groupName, String dn )
throws Exception
{
+ if ( !exists( context, dn ) )
+ {
+ Attributes attributes = new BasicAttributes( true );
+ BasicAttribute objectClass = new BasicAttribute( "objectClass" );
+ objectClass.add( "top" );
+ objectClass.add( "groupOfUniqueNames" );
+ attributes.put( objectClass );
+ attributes.put( "cn", groupName );
+ BasicAttribute basicAttribute = new BasicAttribute( "uniquemember" );
- Attributes attributes = new BasicAttributes( true );
- BasicAttribute objectClass = new BasicAttribute( "objectClass" );
- objectClass.add( "top" );
- objectClass.add( "groupOfUniqueNames" );
- attributes.put( objectClass );
- attributes.put( "cn", groupName );
- BasicAttribute basicAttribute = new BasicAttribute( "uniquemember" );
+ basicAttribute.add( "uid=admin," + suffix );
- basicAttribute.add( "uid=admin," + suffix );
+ attributes.put( basicAttribute );
- attributes.put( basicAttribute );
- context.createSubcontext( dn, attributes );
+ context.createSubcontext( dn, attributes );
+ }
+ else
+ {
+ log.error( "Group {} exists already", dn );
+ }
}
private String createGroupDn( String cn )
@@ -185,71 +284,76 @@ private String createGroupDn( String cn )
}
@Test
- public void getAllGroups()
+ public void getAllGroups( )
throws Exception
{
+ String authorizationHeader = getAdminAuthzHeader( );
try
{
GroupService service = getGroupService( authorizationHeader );
- List allGroups = service.getGroups( Integer.valueOf( 0 ), Integer.valueOf( Integer.MAX_VALUE ) ).getData().stream( ).map( group -> group.getName( ) ).collect( Collectors.toList( ) );
+ List allGroups = service.getGroups( Integer.valueOf( 0 ), Integer.valueOf( Integer.MAX_VALUE ) ).getData( ).stream( ).map( group -> group.getName( ) ).collect( Collectors.toList( ) );
- assertThat( allGroups ).isNotNull().isNotEmpty().hasSize( 3 ).containsAll( groups );
+ assertThat( allGroups ).isNotNull( ).isNotEmpty( ).hasSize( 3 ).containsAll( groups );
}
catch ( Exception e )
{
- log.error( e.getMessage(), e );
+ log.error( e.getMessage( ), e );
throw e;
}
}
@Test
- public void getGroupMappings()
+ public void getGroupMappings( )
throws Exception
{
+
+ String authorizationHeader = getAdminAuthzHeader( );
try
{
GroupService service = getGroupService( authorizationHeader );
- List mappings = service.getGroupMappings();
+ List mappings = service.getGroupMappings( );
- assertThat( mappings ).isNotNull().isNotEmpty().hasSize( 3 );
+ assertThat( mappings ).isNotNull( ).isNotEmpty( ).hasSize( 3 );
}
catch ( Exception e )
{
- log.error( e.getMessage(), e );
+ log.error( e.getMessage( ), e );
throw e;
}
}
@Test
- public void addThenRemove()
+ public void addThenRemove( )
throws Exception
{
+ String authorizationHeader = getAdminAuthzHeader( );
+
try
{
GroupService service = getGroupService( authorizationHeader );
- List mappings = service.getGroupMappings();
+ List mappings = service.getGroupMappings( );
- assertThat( mappings ).isNotNull().isNotEmpty().hasSize( 3 );
+ assertThat( mappings ).isNotNull( ).isNotEmpty( ).hasSize( 3 );
GroupMapping groupMapping = new GroupMapping( "ldap group", Arrays.asList( "redback role" ) );
service.addGroupMapping( groupMapping );
- mappings = service.getGroupMappings();
+ mappings = service.getGroupMappings( );
- assertThat( mappings ).isNotNull().isNotEmpty().hasSize( 4 ).are(
- new Condition()
+ assertThat( mappings ).isNotNull( ).isNotEmpty( ).hasSize( 4 ).are(
+ new Condition( )
{
@Override
public boolean matches( GroupMapping mapping )
{
- if ( StringUtils.equals( "ldap group", mapping.getGroup() ) )
+ if ( StringUtils.equals( "ldap group", mapping.getGroup( ) ) )
{
- assertThat( mapping.getRoleNames() ).isNotNull().isNotEmpty().containsOnly(
+ assertThat( mapping.getRoleNames( ) ).isNotNull( ).isNotEmpty( ).containsOnly(
"redback role" );
return true;
}
@@ -260,13 +364,13 @@ public boolean matches( GroupMapping mapping )
service.removeGroupMapping( "ldap group" );
- mappings = service.getGroupMappings();
+ mappings = service.getGroupMappings( );
- assertThat( mappings ).isNotNull().isNotEmpty().hasSize( 3 );
+ assertThat( mappings ).isNotNull( ).isNotEmpty( ).hasSize( 3 );
}
catch ( Exception e )
{
- log.error( e.getMessage(), e );
+ log.error( e.getMessage( ), e );
throw e;
}
}
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/resources/spring-context.xml b/redback-integrations/redback-rest/redback-rest-services/src/test/resources/spring-context.xml
index e2b584d4..a9f90c86 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/test/resources/spring-context.xml
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/resources/spring-context.xml
@@ -47,6 +47,8 @@
+
+