Adding Bearer token authentication for REST services
This commit is contained in:
parent
9e0e580cce
commit
c51c28a303
26
pom.xml
26
pom.xml
|
@ -329,6 +329,12 @@
|
|||
<artifactId>redback-common-configuration-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.archiva.redback</groupId>
|
||||
<artifactId>redback-authentication-jwt</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-logging</groupId>
|
||||
|
@ -421,6 +427,20 @@
|
|||
<version>${slf4j.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>${mockito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-junit-jupiter</artifactId>
|
||||
<version>${mockito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.easymock</groupId>
|
||||
<artifactId>easymock</artifactId>
|
||||
|
@ -537,6 +557,12 @@
|
|||
<artifactId>jackson-databind</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||
<artifactId>jackson-dataformat-xml</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.cxf</groupId>
|
||||
|
|
|
@ -41,6 +41,10 @@
|
|||
<groupId>org.apache.archiva.redback</groupId>
|
||||
<artifactId>redback-users-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.archiva.redback</groupId>
|
||||
<artifactId>redback-configuration</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.inject</groupId>
|
||||
|
|
|
@ -44,4 +44,5 @@ public class AuthenticationConstants
|
|||
*/
|
||||
public static final int AUTHN_MUST_CHANGE_PASSWORD_EXCEPTION = 4;
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*
|
||||
* <dl>
|
||||
* <dt>required</dt>
|
||||
* <dd>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.</dd>
|
||||
* <dt>requisite</dt>
|
||||
* <dd>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.</dd>
|
||||
* <dt>sufficient</dt>
|
||||
* <dd>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.</dd>
|
||||
* <dt>optional</dt>
|
||||
* <dd>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.</dd>
|
||||
* </dl>
|
||||
*
|
||||
* @author Martin Stockhammer <martin_s@apache.org>
|
||||
* @since 3.0
|
||||
*/
|
||||
public enum AuthenticationControl
|
||||
{
|
||||
SUFFICIENT, OPTIONAL, REQUIRED, REQUISITE
|
||||
}
|
|
@ -27,14 +27,50 @@ import java.util.List;
|
|||
/**
|
||||
* 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<Authenticator> 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<AuthenticatorControl> getControls();
|
||||
|
||||
/**
|
||||
* Sets the list of authenticator controls
|
||||
* @param controlList the list of control instances
|
||||
*/
|
||||
void setControls( List<AuthenticatorControl> controlList);
|
||||
|
||||
/**
|
||||
* Modifies the control for a single authenticator
|
||||
* @param control the authenticator control
|
||||
*/
|
||||
void modifyControl(AuthenticatorControl control);
|
||||
}
|
|
@ -96,7 +96,7 @@ public class AuthenticationResult
|
|||
{
|
||||
if ( authenticationFailureCauses == null )
|
||||
{
|
||||
this.authenticationFailureCauses = new ArrayList<AuthenticationFailureCause>();
|
||||
this.authenticationFailureCauses = new ArrayList<>( );
|
||||
}
|
||||
return authenticationFailureCauses;
|
||||
}
|
||||
|
|
|
@ -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 <martin_s@apache.org>
|
||||
* @since 3.0
|
||||
*/
|
||||
public class AuthenticatorControl implements Comparable<AuthenticatorControl>
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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 <martin_s@apache.org>
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
|
@ -19,6 +19,7 @@ package org.apache.archiva.redback.authentication;
|
|||
* 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.annotation.PostConstruct;
|
|||
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 @@ import java.util.Map;
|
|||
*
|
||||
* @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<Authenticator> authenticators;
|
||||
final private AtomicReference<List<Authenticator>> authenticators = new AtomicReference<>( );
|
||||
final private AtomicReference<Map<String, AuthenticatorControl>> controls = new AtomicReference<>( );
|
||||
final private AtomicReference<Map<String, AuthenticatorControl>> modfiedControls = new AtomicReference<>( );
|
||||
private Map<String, Authenticator> availableAuthenticators;
|
||||
|
||||
@Inject
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@Inject
|
||||
@Named(value = "userManager#default")
|
||||
@Named("userConfiguration#default")
|
||||
private UserConfiguration userConfiguration;
|
||||
|
||||
@Inject
|
||||
@Named( "userManager#default" )
|
||||
private UserManager userManager;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@SuppressWarnings( "unchecked" )
|
||||
@PostConstruct
|
||||
public void initialize() {
|
||||
this.authenticators =
|
||||
new ArrayList<Authenticator>(applicationContext.getBeansOfType(Authenticator.class).values());
|
||||
public void initialize( )
|
||||
{
|
||||
this.availableAuthenticators =
|
||||
applicationContext.getBeansOfType( Authenticator.class ).values( ).stream( ).collect( Collectors.toMap( a -> a.getId( ), authenticator -> authenticator ) );
|
||||
this.modfiedControls.set( new HashMap<>( ) );
|
||||
initializeOrder( );
|
||||
}
|
||||
|
||||
private void initializeOrder() {
|
||||
Stream<AuthenticatorControl> controlStream = initControls( );
|
||||
final List<Authenticator> authenticators = new ArrayList<>( );
|
||||
final Map<String, AuthenticatorControl> controls = new LinkedHashMap<>( );
|
||||
controlStream.forEachOrdered( control -> {
|
||||
authenticators.add( this.availableAuthenticators.get( control.getName( ) ) );
|
||||
controls.put( control.getName( ), control );
|
||||
} );
|
||||
this.authenticators.set( authenticators );
|
||||
this.controls.set( controls );
|
||||
}
|
||||
|
||||
Map<String, AuthenticatorControl> getConfigControls( )
|
||||
{
|
||||
return new HashMap<>( );
|
||||
}
|
||||
|
||||
Map<String, Authenticator> getAvailableAuthenticators() {
|
||||
return this.availableAuthenticators;
|
||||
}
|
||||
|
||||
Map<String, AuthenticatorControl> getModifiedControls() {
|
||||
return this.modfiedControls.get();
|
||||
}
|
||||
|
||||
void setModfiedControls(Map<String, AuthenticatorControl> newControls) {
|
||||
this.modfiedControls.set( newControls );
|
||||
}
|
||||
|
||||
Map<String, AuthenticatorControl> getControlMap() {
|
||||
return controls.get();
|
||||
}
|
||||
|
||||
|
||||
public String getId() {
|
||||
return "Default Authentication Manager - " + this.getClass().getName() + " : managed authenticators - " +
|
||||
knownAuthenticators();
|
||||
private Stream<AuthenticatorControl> initControls( )
|
||||
{
|
||||
Collection<Authenticator> authenticators = getAvailableAuthenticators().values();
|
||||
Map<String, AuthenticatorControl> nondefault = getModifiedControls( );
|
||||
Map<String, AuthenticatorControl> configControlMap = getConfigControls( );
|
||||
Spliterator<Authenticator> spliterator = Spliterators.spliterator( authenticators, Spliterator.NONNULL );
|
||||
return StreamSupport.stream( spliterator, false ).map( authenticator -> {
|
||||
final String id = authenticator.getId( );
|
||||
return nondefault.containsKey( id ) ? nondefault.get(id) : (configControlMap.containsKey( id ) ? configControlMap.get( id ) : new AuthenticatorControl( id, 100, AuthenticationControl.SUFFICIENT, true ));
|
||||
} ).sorted( Collections.reverseOrder( ) );
|
||||
}
|
||||
|
||||
public AuthenticationResult authenticate(AuthenticationDataSource source)
|
||||
throws AccountLockedException, AuthenticationException, MustChangePasswordException {
|
||||
if (authenticators == null || authenticators.size() == 0) {
|
||||
return (new AuthenticationResult(false, null, new AuthenticationException(
|
||||
"no valid authenticators, can't authenticate")));
|
||||
|
||||
public String getId( )
|
||||
{
|
||||
return "Default Authentication Manager - " + this.getClass( ).getName( ) + " : managed authenticators - " +
|
||||
knownAuthenticators( );
|
||||
}
|
||||
|
||||
public AuthenticationResult authenticate( AuthenticationDataSource source )
|
||||
throws AccountLockedException, AuthenticationException, MustChangePasswordException
|
||||
{
|
||||
List<Authenticator> authenticators = this.authenticators.get( );
|
||||
if ( authenticators == null || authenticators.size( ) == 0 )
|
||||
{
|
||||
return ( new AuthenticationResult( false, null, new AuthenticationException(
|
||||
"no valid authenticators, can't authenticate" ) ) );
|
||||
}
|
||||
|
||||
// put AuthenticationResult exceptions in a map
|
||||
List<AuthenticationFailureCause> authnResultErrors = new ArrayList<AuthenticationFailureCause>();
|
||||
for (Authenticator authenticator : authenticators) {
|
||||
if (authenticator.isValid()) {
|
||||
if (authenticator.supportsDataSource(source)) {
|
||||
AuthenticationResult authResult = authenticator.authenticate(source);
|
||||
List<AuthenticationFailureCause> authnResultErrors = new ArrayList<AuthenticationFailureCause>( );
|
||||
for ( Authenticator authenticator : authenticators )
|
||||
{
|
||||
final AuthenticatorControl control = getControlMap( ).get( authenticator.getId( ) );
|
||||
assert control != null;
|
||||
if ( authenticator.isValid( ) && control.isActive())
|
||||
{
|
||||
if ( authenticator.supportsDataSource( source ) )
|
||||
{
|
||||
AuthenticationResult authResult = authenticator.authenticate( source );
|
||||
List<AuthenticationFailureCause> authenticationFailureCauses =
|
||||
authResult.getAuthenticationFailureCauses();
|
||||
authResult.getAuthenticationFailureCauses( );
|
||||
|
||||
if (authResult.isAuthenticated()) {
|
||||
if ( authResult.isAuthenticated( ) )
|
||||
{
|
||||
//olamy: as we can chain various user managers with Archiva
|
||||
// user manager authenticator can lock accounts in the following case :
|
||||
// 2 user managers: ldap and jdo.
|
||||
|
@ -101,17 +178,24 @@ public class DefaultAuthenticationManager
|
|||
// now ldap bind authenticator work but loginAttemptCount has been increased.
|
||||
// so we restore here loginAttemptCount to 0 if in authenticationFailureCauses
|
||||
|
||||
for (AuthenticationFailureCause authenticationFailureCause : authenticationFailureCauses) {
|
||||
User user = authenticationFailureCause.getUser();
|
||||
if (user != null) {
|
||||
if (user.getCountFailedLoginAttempts() > 0) {
|
||||
user.setCountFailedLoginAttempts(0);
|
||||
if (!userManager.isReadOnly()) {
|
||||
try {
|
||||
userManager.updateUser(user);
|
||||
} catch (UserManagerException e) {
|
||||
log.debug(e.getMessage(), e);
|
||||
log.warn("skip error updating user: {}", e.getMessage());
|
||||
for ( AuthenticationFailureCause authenticationFailureCause : authenticationFailureCauses )
|
||||
{
|
||||
User user = authenticationFailureCause.getUser( );
|
||||
if ( user != null )
|
||||
{
|
||||
if ( user.getCountFailedLoginAttempts( ) > 0 )
|
||||
{
|
||||
user.setCountFailedLoginAttempts( 0 );
|
||||
if ( !userManager.isReadOnly( ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
userManager.updateUser( user );
|
||||
}
|
||||
catch ( UserManagerException e )
|
||||
{
|
||||
log.debug( e.getMessage( ), e );
|
||||
log.warn( "skip error updating user: {}", e.getMessage( ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -120,38 +204,72 @@ public class DefaultAuthenticationManager
|
|||
return authResult;
|
||||
}
|
||||
|
||||
if (authenticationFailureCauses != null) {
|
||||
authnResultErrors.addAll(authenticationFailureCauses);
|
||||
} else {
|
||||
if (authResult.getException() != null) {
|
||||
if ( authenticationFailureCauses != null )
|
||||
{
|
||||
authnResultErrors.addAll( authenticationFailureCauses );
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( authResult.getException( ) != null )
|
||||
{
|
||||
authnResultErrors.add(
|
||||
new AuthenticationFailureCause(AuthenticationConstants.AUTHN_RUNTIME_EXCEPTION,
|
||||
authResult.getException().getMessage()));
|
||||
new AuthenticationFailureCause( AuthenticationConstants.AUTHN_RUNTIME_EXCEPTION,
|
||||
authResult.getException( ).getMessage( ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
} else {
|
||||
log.warn("Invalid authenticator found: " + authenticator.getId());
|
||||
}
|
||||
else
|
||||
{
|
||||
log.warn( "Invalid authenticator found: " + authenticator.getId( ) );
|
||||
}
|
||||
}
|
||||
|
||||
return (new AuthenticationResult(false, null, new AuthenticationException(
|
||||
"authentication failed on authenticators: " + knownAuthenticators()), authnResultErrors));
|
||||
return ( new AuthenticationResult( false, null, new AuthenticationException(
|
||||
"authentication failed on authenticators: " + knownAuthenticators( ) ), authnResultErrors ) );
|
||||
}
|
||||
|
||||
public List<Authenticator> getAuthenticators() {
|
||||
return authenticators;
|
||||
@Override
|
||||
public List<AuthenticatorControl> getControls( )
|
||||
{
|
||||
return getControlMap().values().stream().collect( Collectors.toList());
|
||||
}
|
||||
|
||||
private String knownAuthenticators() {
|
||||
StringBuilder strbuf = new StringBuilder();
|
||||
@Override
|
||||
public void setControls( List<AuthenticatorControl> controlList )
|
||||
{
|
||||
setModfiedControls( controlList.stream( ).collect( Collectors.toMap( c -> c.getName( ), c -> c ) ) );
|
||||
initializeOrder( );
|
||||
}
|
||||
|
||||
for (Authenticator authenticator : authenticators) {
|
||||
strbuf.append('(').append(authenticator.getId()).append(") ");
|
||||
@Override
|
||||
public void modifyControl( AuthenticatorControl control )
|
||||
{
|
||||
Map<String, AuthenticatorControl> myControls = getModifiedControls( );
|
||||
if (availableAuthenticators.containsKey( control.getName() )) {
|
||||
myControls.put( control.getName( ), control );
|
||||
} else {
|
||||
log.warn( "Cannot modify control for authenticator {}. It does not exist.", control.getName( ) );
|
||||
}
|
||||
initializeOrder();
|
||||
}
|
||||
|
||||
public List<Authenticator> getAuthenticators( )
|
||||
{
|
||||
return authenticators.get();
|
||||
}
|
||||
|
||||
private String knownAuthenticators( )
|
||||
{
|
||||
StringBuilder strbuf = new StringBuilder( );
|
||||
|
||||
for ( Authenticator authenticator : getAuthenticators() )
|
||||
{
|
||||
strbuf.append( '(' ).append( authenticator.getId( ) ).append( ") " );
|
||||
}
|
||||
|
||||
return strbuf.toString();
|
||||
return strbuf.toString( );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
package org.apache.archiva.redback.authentication.jwt;
|
||||
|
||||
/*
|
||||
* 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 <martin_s@apache.org>
|
||||
*/
|
||||
public enum BearerError
|
||||
{
|
||||
INVALID_REQUEST("invalid_request",1024),INVALID_TOKEN("invalid_token", 1025), INSUFFICIENT_SCOPE( "insufficient_scope", 1026 );
|
||||
|
||||
protected String errorString;
|
||||
protected int id;
|
||||
BearerError(String errorString, int id) {
|
||||
this.errorString = errorString;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return this.errorString;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public static BearerError get(int id) {
|
||||
final BearerError[] values = BearerError.values();
|
||||
for ( int i = 0 ; i<values.length; i++) {
|
||||
if (values[i].id==id) {
|
||||
return values[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -20,20 +20,25 @@ package org.apache.archiva.redback.authentication.jwt;
|
|||
*/
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import io.jsonwebtoken.Jws;
|
||||
import io.jsonwebtoken.JwsHeader;
|
||||
import io.jsonwebtoken.Jwt;
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import io.jsonwebtoken.JwtParser;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.MalformedJwtException;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.SigningKeyResolverAdapter;
|
||||
import io.jsonwebtoken.UnsupportedJwtException;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import io.jsonwebtoken.security.SignatureException;
|
||||
import org.apache.archiva.redback.authentication.AbstractAuthenticator;
|
||||
import org.apache.archiva.redback.authentication.AuthenticationDataSource;
|
||||
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.Authenticator;
|
||||
import org.apache.archiva.redback.authentication.BearerTokenAuthenticationDataSource;
|
||||
import org.apache.archiva.redback.authentication.SimpleTokenData;
|
||||
import org.apache.archiva.redback.authentication.StringToken;
|
||||
import org.apache.archiva.redback.authentication.Token;
|
||||
|
@ -70,6 +75,7 @@ import java.security.spec.PKCS8EncodedKeySpec;
|
|||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
|
@ -179,12 +185,12 @@ public class JwtAuthenticator extends AbstractAuthenticator implements Authentic
|
|||
{
|
||||
KeyPair pair = getKeyPair( keyId );
|
||||
if (pair == null) {
|
||||
throw new JwtException( "Key ID not found in current list. Verification failed." );
|
||||
throw new JwtKeyIdNotFoundException( "Key ID not found in current list. Verification failed." );
|
||||
}
|
||||
key = pair.getPublic( );
|
||||
}
|
||||
if (key==null) {
|
||||
throw new JwtException( "Key ID not found in current list. Verification failed." );
|
||||
throw new JwtKeyIdNotFoundException( "Key ID not found in current list. Verification failed." );
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
@ -197,8 +203,9 @@ public class JwtAuthenticator extends AbstractAuthenticator implements Authentic
|
|||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init( )
|
||||
public void init( ) throws AuthenticationException
|
||||
{
|
||||
super.initialize();
|
||||
this.keyCounter = new AtomicLong( System.currentTimeMillis( ) );
|
||||
this.keystoreType = userConfiguration.getString( AUTHENTICATION_JWT_KEYSTORETYPE, AUTHENTICATION_JWT_KEYSTORETYPE_MEMORY );
|
||||
this.fileStore = this.keystoreType.equals( AUTHENTICATION_JWT_KEYSTORETYPE_PLAINFILE );
|
||||
|
@ -523,7 +530,7 @@ public class JwtAuthenticator extends AbstractAuthenticator implements Authentic
|
|||
@Override
|
||||
public boolean supportsDataSource( AuthenticationDataSource source )
|
||||
{
|
||||
return ( source instanceof TokenBasedAuthenticationDataSource );
|
||||
return ( source instanceof BearerTokenAuthenticationDataSource );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -532,27 +539,28 @@ public class JwtAuthenticator extends AbstractAuthenticator implements Authentic
|
|||
* @return the authentication result
|
||||
* @throws AuthenticationException if the source is no {@link TokenBasedAuthenticationDataSource}
|
||||
*/
|
||||
@Override
|
||||
public AuthenticationResult authenticate( AuthenticationDataSource source ) throws AuthenticationException
|
||||
public AuthenticationResult authenticate( BearerTokenAuthenticationDataSource source ) throws AuthenticationException
|
||||
{
|
||||
if ( source instanceof TokenBasedAuthenticationDataSource )
|
||||
{
|
||||
TokenBasedAuthenticationDataSource tSource = (TokenBasedAuthenticationDataSource) source;
|
||||
String jwt = tSource.getToken( );
|
||||
String jwt = source.getTokenData( );
|
||||
AuthenticationResult result;
|
||||
try
|
||||
{
|
||||
String subject = verify( jwt );
|
||||
result = new AuthenticationResult( true, subject, null );
|
||||
} catch (AuthenticationException e) {
|
||||
result = new AuthenticationResult( false, source.getUsername(), e );
|
||||
} catch ( TokenAuthenticationException e) {
|
||||
AuthenticationFailureCause cause = new AuthenticationFailureCause(e.getError().getId(), e.getMessage() );
|
||||
result = new AuthenticationResult( false, source.getUsername(), e, Arrays.asList( cause ) );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticationResult authenticate(AuthenticationDataSource dataSource) throws AuthenticationException
|
||||
{
|
||||
if (dataSource instanceof BearerTokenAuthenticationDataSource) {
|
||||
return this.authenticate( (BearerTokenAuthenticationDataSource) dataSource );
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new AuthenticationException( "The provided authentication source is not suitable for this authenticator" );
|
||||
}
|
||||
throw new AuthenticationException( "Authentication datasource not supported by this JwtAuthenticator" );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -701,7 +709,7 @@ public class JwtAuthenticator extends AbstractAuthenticator implements Authentic
|
|||
* @return the subject of the JWT
|
||||
* @throws AuthenticationException if the verification failed
|
||||
*/
|
||||
public String verify( String token ) throws AuthenticationException
|
||||
public String verify( String token ) throws TokenAuthenticationException
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -709,14 +717,32 @@ public class JwtAuthenticator extends AbstractAuthenticator implements Authentic
|
|||
String subject = signature.getBody( ).getSubject( );
|
||||
if ( StringUtils.isEmpty( subject ) )
|
||||
{
|
||||
throw new AuthenticationException( "Subject in JWT is empty" );
|
||||
throw new TokenAuthenticationException( BearerError.INVALID_TOKEN, "contains no subject" );
|
||||
}
|
||||
return subject;
|
||||
}
|
||||
catch ( JwtException e )
|
||||
catch ( ExpiredJwtException e )
|
||||
{
|
||||
throw new AuthenticationException( e.getMessage( ), e );
|
||||
throw new TokenAuthenticationException( BearerError.INVALID_TOKEN, "token expired" );
|
||||
}
|
||||
catch ( SignatureException e ) {
|
||||
throw new TokenAuthenticationException( BearerError.INVALID_TOKEN, "token signature does not match" );
|
||||
}
|
||||
catch ( UnsupportedJwtException e) {
|
||||
throw new TokenAuthenticationException( BearerError.INVALID_TOKEN, "jwt is unsupported" );
|
||||
}
|
||||
catch ( MalformedJwtException e)
|
||||
{
|
||||
throw new TokenAuthenticationException( BearerError.INVALID_TOKEN, "malformed token content" );
|
||||
}
|
||||
catch (JwtKeyIdNotFoundException e) {
|
||||
throw new TokenAuthenticationException( BearerError.INVALID_TOKEN, "signer key does not exist" );
|
||||
}
|
||||
catch ( JwtException e) {
|
||||
log.debug( "Unknown JwtException {}, {}", e.getClass( ), e.getMessage( ) );
|
||||
throw new TokenAuthenticationException( BearerError.INVALID_TOKEN, "unknown error " + e.getMessage( ) );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package org.apache.archiva.redback.authentication.jwt;
|
||||
|
||||
/*
|
||||
* 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 io.jsonwebtoken.JwtException;
|
||||
|
||||
/**
|
||||
* Exception is thrown, when a key with the given id is not in the current list
|
||||
* @author Martin Stockhammer <martin_s@apache.org>
|
||||
*/
|
||||
public class JwtKeyIdNotFoundException extends JwtException
|
||||
{
|
||||
public JwtKeyIdNotFoundException( String message )
|
||||
{
|
||||
super( message );
|
||||
}
|
||||
|
||||
public JwtKeyIdNotFoundException( String message, Throwable cause )
|
||||
{
|
||||
super( message, cause );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package org.apache.archiva.redback.authentication.jwt;
|
||||
|
||||
/*
|
||||
* 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 <martin_s@apache.org>
|
||||
*/
|
||||
public class TokenAuthenticationException extends Exception
|
||||
{
|
||||
final private BearerError error;
|
||||
|
||||
public TokenAuthenticationException( BearerError error)
|
||||
{
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public TokenAuthenticationException( BearerError error, String message )
|
||||
{
|
||||
super( message );
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public TokenAuthenticationException( BearerError error, String message, Throwable cause )
|
||||
{
|
||||
super( message, cause );
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public TokenAuthenticationException( BearerError error, Throwable cause )
|
||||
{
|
||||
super( cause );
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public TokenAuthenticationException( BearerError error, String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace )
|
||||
{
|
||||
super( message, cause, enableSuppression, writableStackTrace );
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public BearerError getError( )
|
||||
{
|
||||
return error;
|
||||
}
|
||||
}
|
|
@ -21,22 +21,18 @@ package org.apache.archiva.redback.authentication.jwt;
|
|||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jws;
|
||||
import io.jsonwebtoken.JwsHeader;
|
||||
import org.apache.archiva.components.registry.Registry;
|
||||
import org.apache.archiva.components.registry.RegistryException;
|
||||
import org.apache.archiva.components.registry.commons.CommonsConfigurationRegistry;
|
||||
import org.apache.archiva.redback.authentication.AuthenticationException;
|
||||
import org.apache.archiva.redback.authentication.AuthenticationResult;
|
||||
import org.apache.archiva.redback.authentication.BearerTokenAuthenticationDataSource;
|
||||
import org.apache.archiva.redback.authentication.PasswordBasedAuthenticationDataSource;
|
||||
import org.apache.archiva.redback.authentication.Token;
|
||||
import org.apache.archiva.redback.authentication.TokenBasedAuthenticationDataSource;
|
||||
import org.apache.archiva.redback.configuration.DefaultUserConfiguration;
|
||||
import org.apache.archiva.redback.configuration.UserConfiguration;
|
||||
import org.apache.archiva.redback.configuration.UserConfigurationException;
|
||||
import org.apache.commons.configuration2.BaseConfiguration;
|
||||
import org.apache.commons.configuration2.Configuration;
|
||||
import org.apache.commons.configuration2.builder.BasicConfigurationBuilder;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
@ -56,7 +52,7 @@ public abstract class AbstractJwtTest
|
|||
protected CommonsConfigurationRegistry registry;
|
||||
protected BaseConfiguration saveConfig;
|
||||
|
||||
protected void init( Map<String, String> parameters) throws UserConfigurationException, RegistryException
|
||||
protected void init( Map<String, String> parameters) throws UserConfigurationException, RegistryException, AuthenticationException
|
||||
{
|
||||
this.registry = new CommonsConfigurationRegistry( );
|
||||
String baseDir = System.getProperty( "basedir", "" );
|
||||
|
@ -95,7 +91,8 @@ public abstract class AbstractJwtTest
|
|||
@Test
|
||||
void supportsDataSource( )
|
||||
{
|
||||
assertTrue( jwtAuthenticator.supportsDataSource( new TokenBasedAuthenticationDataSource( ) ) );
|
||||
assertTrue( jwtAuthenticator.supportsDataSource( new BearerTokenAuthenticationDataSource( ) ) );
|
||||
assertFalse( jwtAuthenticator.supportsDataSource( new TokenBasedAuthenticationDataSource( ) ) );
|
||||
assertFalse( jwtAuthenticator.supportsDataSource( new PasswordBasedAuthenticationDataSource( ) ) );
|
||||
}
|
||||
|
||||
|
@ -151,7 +148,7 @@ public abstract class AbstractJwtTest
|
|||
}
|
||||
|
||||
@Test
|
||||
void verify( ) throws AuthenticationException
|
||||
void verify( ) throws TokenAuthenticationException
|
||||
{
|
||||
Token token = jwtAuthenticator.generateToken( "frodo_baggins" );
|
||||
assertEquals( "frodo_baggins", jwtAuthenticator.verify( token.getData( ) ) );
|
||||
|
@ -183,12 +180,12 @@ public abstract class AbstractJwtTest
|
|||
}
|
||||
|
||||
@Test
|
||||
void invalidKeySignature() throws AuthenticationException
|
||||
void invalidKeySignature() throws TokenAuthenticationException
|
||||
{
|
||||
Token token = jwtAuthenticator.generateToken( "samwise_gamgee" );
|
||||
assertEquals( "samwise_gamgee", jwtAuthenticator.verify( token.getData( ) ) );
|
||||
jwtAuthenticator.revokeSigningKeys( );
|
||||
assertThrows( AuthenticationException.class, ( ) -> {
|
||||
assertThrows( TokenAuthenticationException.class, ( ) -> {
|
||||
jwtAuthenticator.verify( token.getData( ) );
|
||||
} );
|
||||
}
|
||||
|
@ -202,7 +199,7 @@ public abstract class AbstractJwtTest
|
|||
{
|
||||
jwtAuthenticator.setTokenLifetime( Duration.ofNanos( 0 ) );
|
||||
Token token = jwtAuthenticator.generateToken( "samwise_gamgee" );
|
||||
assertThrows( AuthenticationException.class, ( ) -> {
|
||||
assertThrows( TokenAuthenticationException.class, ( ) -> {
|
||||
jwtAuthenticator.verify( token.getData( ) );
|
||||
} );
|
||||
} finally
|
||||
|
@ -216,9 +213,8 @@ public abstract class AbstractJwtTest
|
|||
void validAuthenticate() throws AuthenticationException
|
||||
{
|
||||
Token token = jwtAuthenticator.generateToken( "bilbo_baggins" );
|
||||
TokenBasedAuthenticationDataSource source = new TokenBasedAuthenticationDataSource( );
|
||||
source.setPrincipal( "bilbo_baggins" );
|
||||
source.setToken( token.getData() );
|
||||
BearerTokenAuthenticationDataSource source = new BearerTokenAuthenticationDataSource( );
|
||||
source.setTokenData( token.getData() );
|
||||
AuthenticationResult result = jwtAuthenticator.authenticate( source );
|
||||
assertNotNull( result );
|
||||
assertTrue( result.isAuthenticated( ) );
|
||||
|
@ -228,13 +224,11 @@ public abstract class AbstractJwtTest
|
|||
@Test
|
||||
void invalidAuthenticate() throws AuthenticationException
|
||||
{
|
||||
TokenBasedAuthenticationDataSource source = new TokenBasedAuthenticationDataSource( );
|
||||
source.setPrincipal( "bilbo_baggins" );
|
||||
source.setToken( "invalidToken" );
|
||||
BearerTokenAuthenticationDataSource source = new BearerTokenAuthenticationDataSource( );
|
||||
source.setTokenData( "invalidToken" );
|
||||
AuthenticationResult result = jwtAuthenticator.authenticate( source );
|
||||
assertNotNull( result );
|
||||
assertFalse( result.isAuthenticated( ) );
|
||||
assertEquals( "bilbo_baggins", result.getPrincipal( ) );
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.apache.archiva.redback.authentication.jwt;
|
|||
*/
|
||||
|
||||
import org.apache.archiva.components.registry.RegistryException;
|
||||
import org.apache.archiva.redback.authentication.AuthenticationException;
|
||||
import org.apache.archiva.redback.configuration.UserConfigurationException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
|
@ -46,7 +47,7 @@ class JwtAuthenticatorFilebasedPublicKeyTest extends AbstractJwtTest
|
|||
{
|
||||
|
||||
@BeforeEach
|
||||
void init() throws RegistryException, UserConfigurationException
|
||||
void init() throws RegistryException, UserConfigurationException, AuthenticationException
|
||||
{
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put( AUTHENTICATION_JWT_KEYSTORETYPE, AUTHENTICATION_JWT_KEYSTORETYPE_PLAINFILE );
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.apache.archiva.redback.authentication.jwt;
|
|||
*/
|
||||
|
||||
import org.apache.archiva.components.registry.RegistryException;
|
||||
import org.apache.archiva.redback.authentication.AuthenticationException;
|
||||
import org.apache.archiva.redback.configuration.UserConfigurationException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
|
@ -48,7 +49,7 @@ class JwtAuthenticatorFilebasedTest extends AbstractJwtTest
|
|||
{
|
||||
|
||||
@BeforeEach
|
||||
void init() throws RegistryException, UserConfigurationException
|
||||
void init() throws RegistryException, UserConfigurationException, AuthenticationException
|
||||
{
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put( AUTHENTICATION_JWT_KEYSTORETYPE, AUTHENTICATION_JWT_KEYSTORETYPE_PLAINFILE );
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.apache.archiva.redback.authentication.jwt;
|
|||
*/
|
||||
|
||||
import org.apache.archiva.components.registry.RegistryException;
|
||||
import org.apache.archiva.redback.authentication.AuthenticationException;
|
||||
import org.apache.archiva.redback.configuration.UserConfigurationException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
|
@ -40,7 +41,7 @@ import static org.junit.jupiter.api.Assertions.assertNull;
|
|||
class JwtAuthenticatorMemorybasedTest extends AbstractJwtTest
|
||||
{
|
||||
@BeforeEach
|
||||
void init() throws RegistryException, UserConfigurationException
|
||||
void init() throws RegistryException, UserConfigurationException, AuthenticationException
|
||||
{
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put( AUTHENTICATION_JWT_KEYSTORETYPE, AUTHENTICATION_JWT_KEYSTORETYPE_MEMORY );
|
||||
|
|
|
@ -22,6 +22,8 @@ import org.apache.archiva.redback.keys.AuthenticationKey;
|
|||
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import java.time.Instant;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
|
@ -33,8 +35,8 @@ import java.util.Date;
|
|||
public class Token
|
||||
{
|
||||
String key;
|
||||
Instant created;
|
||||
Instant expires;
|
||||
OffsetDateTime created;
|
||||
OffsetDateTime expires;
|
||||
String principal;
|
||||
String purpose;
|
||||
|
||||
|
@ -45,8 +47,8 @@ public class Token
|
|||
public static Token of( AuthenticationKey key ) {
|
||||
Token token = new Token( );
|
||||
token.setKey( key.getKey() );
|
||||
token.setCreated( key.getDateCreated().toInstant() );
|
||||
token.setExpires( key.getDateExpires().toInstant() );
|
||||
token.setCreatedFromInstant( key.getDateCreated().toInstant() );
|
||||
token.setExpiresFromInstant( key.getDateExpires().toInstant() );
|
||||
token.setPrincipal( key.getForPrincipal() );
|
||||
token.setPurpose( key.getPurpose() );
|
||||
return token;
|
||||
|
@ -56,8 +58,8 @@ public class Token
|
|||
{
|
||||
Token token = new Token( );
|
||||
token.setKey( key );
|
||||
token.setCreated( created.toInstant( ) );
|
||||
token.setExpires( expires.toInstant( ) );
|
||||
token.setCreatedFromInstant( created.toInstant( ) );
|
||||
token.setExpiresFromInstant( expires.toInstant( ) );
|
||||
token.setPrincipal( principal );
|
||||
token.setPrincipal( purpose );
|
||||
return token;
|
||||
|
@ -67,8 +69,8 @@ public class Token
|
|||
{
|
||||
Token token = new Token( );
|
||||
token.setKey( key );
|
||||
token.setCreated( created );
|
||||
token.setExpires( expires );
|
||||
token.setCreatedFromInstant( created );
|
||||
token.setExpiresFromInstant( expires );
|
||||
token.setPrincipal( principal );
|
||||
token.setPrincipal( purpose );
|
||||
return token;
|
||||
|
@ -84,22 +86,31 @@ public class Token
|
|||
this.key = key;
|
||||
}
|
||||
|
||||
public Instant getCreated( )
|
||||
public OffsetDateTime getCreated( )
|
||||
{
|
||||
return created;
|
||||
}
|
||||
|
||||
public void setCreated( Instant created )
|
||||
public void setCreatedFromInstant( Instant created )
|
||||
{
|
||||
this.created = OffsetDateTime.ofInstant( created, ZoneId.of( "UTC" ) );
|
||||
}
|
||||
|
||||
public void setCreated( OffsetDateTime created )
|
||||
{
|
||||
this.created = created;
|
||||
}
|
||||
|
||||
public Instant getExpires( )
|
||||
public OffsetDateTime getExpires( )
|
||||
{
|
||||
return expires;
|
||||
}
|
||||
|
||||
public void setExpires( Instant expires )
|
||||
public void setExpiresFromInstant( Instant expires )
|
||||
{
|
||||
this.expires = OffsetDateTime.ofInstant( expires, ZoneId.of( "UTC" ) );
|
||||
}
|
||||
|
||||
public void setExpires( OffsetDateTime expires )
|
||||
{
|
||||
this.expires = expires;
|
||||
}
|
||||
|
|
|
@ -50,9 +50,9 @@ public class RedbackServiceException
|
|||
errorMessages.add( errorMessage );
|
||||
}
|
||||
|
||||
public RedbackServiceException( ErrorMessage errorMessage, int httpErrorCode )
|
||||
public RedbackServiceException( ErrorMessage errorMessage, int httpResponseCode )
|
||||
{
|
||||
this.httpErrorCode = httpErrorCode;
|
||||
this.httpErrorCode = httpResponseCode;
|
||||
errorMessages.add( errorMessage );
|
||||
}
|
||||
|
||||
|
@ -61,6 +61,12 @@ public class RedbackServiceException
|
|||
errorMessages.addAll( errorMessage );
|
||||
}
|
||||
|
||||
public RedbackServiceException( List<ErrorMessage> errorMessage, int httpResponseCode )
|
||||
{
|
||||
this.httpErrorCode = httpResponseCode;
|
||||
errorMessages.addAll( errorMessage );
|
||||
}
|
||||
|
||||
public int getHttpErrorCode()
|
||||
{
|
||||
return httpErrorCode;
|
||||
|
|
|
@ -19,17 +19,14 @@ package org.apache.archiva.redback.rest.api.services.v2;
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import org.apache.archiva.redback.authorization.RedbackAuthorization;
|
||||
import org.apache.archiva.redback.rest.api.model.ActionStatus;
|
||||
import org.apache.archiva.redback.rest.api.model.AuthenticationKeyResult;
|
||||
import org.apache.archiva.redback.rest.api.model.LoginRequest;
|
||||
import org.apache.archiva.redback.rest.api.model.PingResult;
|
||||
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.model.UserLogin;
|
||||
import org.apache.archiva.redback.rest.api.services.RedbackServiceException;
|
||||
|
||||
import javax.ws.rs.GET;
|
||||
|
|
|
@ -94,15 +94,24 @@
|
|||
<groupId>org.apache.archiva.redback</groupId>
|
||||
<artifactId>redback-system</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.archiva.redback</groupId>
|
||||
<artifactId>redback-authentication-jwt</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.archiva.components.cache</groupId>
|
||||
<artifactId>archiva-components-spring-cache-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
<groupId>org.apache.archiva.redback</groupId>
|
||||
<artifactId>redback-common-integrations</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.archiva.redback</groupId>
|
||||
<artifactId>redback-rest-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
|
@ -113,25 +122,47 @@
|
|||
<artifactId>ehcache</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.persistence</groupId>
|
||||
<artifactId>jakarta.persistence-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.transaction</groupId>
|
||||
<artifactId>jakarta.transaction-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.xml.bind</groupId>
|
||||
<artifactId>jakarta.xml.bind-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.inject</groupId>
|
||||
<artifactId>jakarta.inject-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.annotation</groupId>
|
||||
<artifactId>jakarta.annotation-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.mail</groupId>
|
||||
<artifactId>jakarta.mail-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.ws.rs</groupId>
|
||||
<artifactId>jakarta.ws.rs-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.sun.mail</groupId>
|
||||
<artifactId>jakarta.mail</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.archiva.redback</groupId>
|
||||
<artifactId>redback-rest-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.archiva.redback</groupId>
|
||||
<artifactId>redback-common-integrations</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
|
@ -148,6 +179,7 @@
|
|||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cxf</groupId>
|
||||
|
@ -177,7 +209,6 @@
|
|||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||
<artifactId>jackson-dataformat-xml</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
@ -192,6 +223,11 @@
|
|||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.archiva.redback</groupId>
|
||||
<artifactId>redback-users-memory</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.archiva.redback</groupId>
|
||||
<artifactId>redback-rbac-jpa</artifactId>
|
||||
|
@ -305,39 +341,19 @@
|
|||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.persistence</groupId>
|
||||
<artifactId>jakarta.persistence-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.transaction</groupId>
|
||||
<artifactId>jakarta.transaction-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.xml.bind</groupId>
|
||||
<artifactId>jakarta.xml.bind-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.inject</groupId>
|
||||
<artifactId>jakarta.inject-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.annotation</groupId>
|
||||
<artifactId>jakarta.annotation-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.mail</groupId>
|
||||
<artifactId>jakarta.mail-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.ws.rs</groupId>
|
||||
<artifactId>jakarta.ws.rs-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.sun.mail</groupId>
|
||||
<artifactId>jakarta.mail</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
|
|
@ -18,14 +18,19 @@ package org.apache.archiva.redback.rest.services;
|
|||
* 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 class RedbackRequestInformation
|
|||
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 class RedbackRequestInformation
|
|||
{
|
||||
this.remoteAddr = remoteAddr;
|
||||
}
|
||||
|
||||
public SecuritySession getSecuritySession( )
|
||||
{
|
||||
return securitySession;
|
||||
}
|
||||
|
||||
public void setSecuritySession( SecuritySession securitySession )
|
||||
{
|
||||
this.securitySession = securitySession;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,9 @@ import org.apache.archiva.redback.authorization.RedbackAuthorization;
|
|||
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<Method, RedbackAuthorization> 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 abstract class AbstractInterceptor
|
|||
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 @@ public abstract class AbstractInterceptor
|
|||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
|
@ -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 <martin_s@apache.org>
|
||||
* @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( ) );
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -50,6 +50,7 @@ public class JacksonJsonConfigurator
|
|||
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" ) );
|
||||
|
||||
|
|
|
@ -24,13 +24,17 @@ import org.apache.archiva.redback.authorization.AuthorizationException;
|
|||
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 @@ import javax.ws.rs.ext.Provider;
|
|||
*/
|
||||
@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 class PermissionsInterceptor
|
|||
&& !( 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 class PermissionsInterceptor
|
|||
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 );
|
||||
|
|
|
@ -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 <martin_s@apache.org>
|
||||
*/
|
||||
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;
|
||||
}
|
|
@ -34,6 +34,7 @@ import org.slf4j.LoggerFactory;
|
|||
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 @@ import java.util.List;
|
|||
*/
|
||||
@Provider
|
||||
@Service( "requestValidationInterceptor#rest" )
|
||||
@Priority( Priorities.PRECHECK )
|
||||
public class RequestValidationInterceptor
|
||||
extends AbstractInterceptor
|
||||
implements ContainerRequestFilter
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.slf4j.Logger;
|
|||
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 @@ import java.io.IOException;
|
|||
@Service( "threadLocalUserCleaner#rest" )
|
||||
@Provider
|
||||
@PreMatching
|
||||
@Priority( Priorities.INITIALIZE )
|
||||
public class ThreadLocalUserCleaner
|
||||
implements ContainerResponseFilter
|
||||
{
|
||||
|
|
|
@ -22,8 +22,8 @@ package org.apache.archiva.redback.rest.services.v2;
|
|||
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 org.springframework.stereotype.Service;
|
|||
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 class DefaultAuthenticationService
|
|||
|
||||
@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 class DefaultAuthenticationService
|
|||
|
||||
@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 class DefaultAuthenticationService
|
|||
{
|
||||
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<ErrorMessage> errorMessages = new ArrayList<ErrorMessage>();
|
||||
|
@ -190,33 +187,42 @@ public class DefaultAuthenticationService
|
|||
{
|
||||
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<ErrorMessage> errorMessages =
|
||||
Arrays.asList( new ErrorMessage().message( "UserManagerException: " + e.getMessage() ) );
|
||||
throw new RedbackServiceException( errorMessages );
|
||||
|
|
|
@ -88,7 +88,7 @@
|
|||
<jaxrs:providers>
|
||||
<ref bean="jsonProvider"/>
|
||||
<ref bean="xmlProvider"/>
|
||||
<ref bean="authenticationInterceptor#rest"/>
|
||||
<ref bean="bearerAuthInterceptor#rest"/>
|
||||
<ref bean="permissionInterceptor#rest"/>
|
||||
<ref bean="redbackServiceExceptionMapper"/>
|
||||
<ref bean="passwordRuleViolationExceptionMapper"/>
|
||||
|
|
|
@ -19,6 +19,8 @@ package org.apache.archiva.redback.rest.services;
|
|||
* 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 abstract class AbstractRestServicesTest
|
|||
}
|
||||
}
|
||||
|
||||
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 @@ public abstract class AbstractRestServicesTest
|
|||
{
|
||||
return JAXRSClientFactory.create(
|
||||
"http://localhost:" + getServerPort()+ "/" + getRestServicesPath() + "/fakeCreateAdminService/",
|
||||
FakeCreateAdminService.class, Collections.singletonList( new JacksonJaxbJsonProvider() ) );
|
||||
FakeCreateAdminService.class, Collections.singletonList( getJsonProvider() ) );
|
||||
}
|
||||
|
||||
@After
|
||||
|
@ -241,7 +251,7 @@ public abstract class AbstractRestServicesTest
|
|||
{
|
||||
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 @@ public abstract class AbstractRestServicesTest
|
|||
{
|
||||
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 @@ public abstract class AbstractRestServicesTest
|
|||
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() );
|
||||
|
|
|
@ -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 <martin_s@apache.org>
|
||||
*/
|
||||
@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 );
|
||||
}
|
||||
|
||||
}
|
|
@ -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> server = new AtomicReference<>();
|
||||
private static AtomicReference<ServerConnector> 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 <code>rest.test.timeout</code>.
|
||||
* @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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -21,30 +21,50 @@ package org.apache.archiva.redback.rest.services.v2;
|
|||
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 class AuthenticationServiceTest
|
|||
{
|
||||
|
||||
// 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 class AuthenticationServiceTest
|
|||
}
|
||||
finally
|
||||
{
|
||||
getUserService( authorizationHeader ).deleteUser( "toto" );
|
||||
getUserService( authorizationHeader ).removeFromCache( "toto" );
|
||||
assertNull( getUserService( authorizationHeader ).getUser( "toto" ) );
|
||||
deleteUser( "toto" );
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,19 +22,23 @@ import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
|
|||
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 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
/**
|
||||
* @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 @@ public class GroupServiceTest
|
|||
}
|
||||
|
||||
@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 @@ public class GroupServiceTest
|
|||
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 @@ public class GroupServiceTest
|
|||
}
|
||||
|
||||
@Test
|
||||
public void getAllGroups()
|
||||
public void getAllGroups( )
|
||||
throws Exception
|
||||
{
|
||||
String authorizationHeader = getAdminAuthzHeader( );
|
||||
|
||||
try
|
||||
{
|
||||
GroupService service = getGroupService( authorizationHeader );
|
||||
|
||||
List<String> allGroups = service.getGroups( Integer.valueOf( 0 ), Integer.valueOf( Integer.MAX_VALUE ) ).getData().stream( ).map( group -> group.getName( ) ).collect( Collectors.toList( ) );
|
||||
List<String> 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<GroupMapping> mappings = service.getGroupMappings();
|
||||
List<GroupMapping> 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<GroupMapping> mappings = service.getGroupMappings();
|
||||
List<GroupMapping> 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<GroupMapping>()
|
||||
assertThat( mappings ).isNotNull( ).isNotEmpty( ).hasSize( 4 ).are(
|
||||
new Condition<GroupMapping>( )
|
||||
{
|
||||
@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 class GroupServiceTest
|
|||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,6 +47,8 @@
|
|||
</property>
|
||||
</bean>
|
||||
|
||||
<alias name="authenticator#jwt" alias="jwtAuthenticator#default" />
|
||||
|
||||
|
||||
|
||||
<alias name="authorizer#rbac" alias="authorizer#default"/>
|
||||
|
|
Loading…
Reference in New Issue