Adding refresh token for authentication
This commit is contained in:
parent
f88a365bc8
commit
15ad042ac1
|
@ -61,6 +61,22 @@ public final class SimpleTokenData implements Serializable, TokenData {
|
|||
this.nonce = nonce;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new token info instance for the given user.
|
||||
* The lifetime in milliseconds defines the invalidation date by
|
||||
* adding the lifetime to the current time of instantiation.
|
||||
*
|
||||
* @param user The user name
|
||||
* @param lifetime The number of milliseconds after that the token is invalid
|
||||
* @param nonce Should be a random number and different for each instance.
|
||||
*/
|
||||
public SimpleTokenData(final String user, final Duration lifetime, final long nonce) {
|
||||
this.user=user;
|
||||
this.created = Instant.now( );
|
||||
this.validBefore = created.plus( lifetime );
|
||||
this.nonce = nonce;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getUser() {
|
||||
return user;
|
||||
|
|
|
@ -26,23 +26,34 @@ package org.apache.archiva.redback.authentication;
|
|||
public class StringToken implements Token
|
||||
{
|
||||
final TokenData metadata;
|
||||
final String token;
|
||||
final String data;
|
||||
final String id;
|
||||
final TokenType type;
|
||||
|
||||
public StringToken(String tokenData, TokenData metadata) {
|
||||
this.token = tokenData;
|
||||
public StringToken(String id, String tokenData, TokenData metadata) {
|
||||
this.id = id;
|
||||
this.data = tokenData;
|
||||
this.metadata = metadata;
|
||||
this.type = TokenType.ACCESS_TOKEN;
|
||||
}
|
||||
|
||||
public StringToken(TokenType type, String id, String tokenData, TokenData metadata) {
|
||||
this.id = id;
|
||||
this.data = tokenData;
|
||||
this.metadata = metadata;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getData( )
|
||||
{
|
||||
return token;
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getBytes( )
|
||||
{
|
||||
return token.getBytes( );
|
||||
return data.getBytes( );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -50,4 +61,16 @@ public class StringToken implements Token
|
|||
{
|
||||
return metadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId( )
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TokenType getType( )
|
||||
{
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,9 +26,34 @@ package org.apache.archiva.redback.authentication;
|
|||
public interface Token
|
||||
{
|
||||
|
||||
/**
|
||||
* The token id, if it exists, otherwise a empty string.
|
||||
* @return
|
||||
*/
|
||||
String getId();
|
||||
|
||||
/**
|
||||
* Returns the token type (access or refresh token)
|
||||
* @return the token type
|
||||
*/
|
||||
TokenType getType();
|
||||
|
||||
/**
|
||||
* The string representation of the token data. It depends on the token algorithm,
|
||||
* what kind of string conversion is used (e.g. Base64)
|
||||
* @return the token string
|
||||
*/
|
||||
String getData();
|
||||
|
||||
/**
|
||||
* The token as byte array
|
||||
* @return
|
||||
*/
|
||||
byte[] getBytes();
|
||||
|
||||
/**
|
||||
* The token meta data, like expiration time.
|
||||
* @return the metadata
|
||||
*/
|
||||
TokenData getMetadata();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @author Martin Stockhammer <martin_s@apache.org>
|
||||
*/
|
||||
public enum TokenType
|
||||
{
|
||||
REFRESH_TOKEN("refresh_token"), ACCESS_TOKEN( "access_token" ), ALL( "*" ),;
|
||||
|
||||
private String claim;
|
||||
|
||||
TokenType( String claim )
|
||||
{
|
||||
this.claim = claim;
|
||||
}
|
||||
|
||||
public String getClaim() {
|
||||
return this.claim;
|
||||
}
|
||||
|
||||
public static TokenType ofClaim(String claim) {
|
||||
TokenType[] vals = values( );
|
||||
for (int i=0; i< vals.length; i++) {
|
||||
if (vals[i].getClaim().equals(claim)) {
|
||||
return vals[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -21,12 +21,14 @@ package org.apache.archiva.redback.authentication.jwt;
|
|||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import io.jsonwebtoken.IncorrectClaimException;
|
||||
import io.jsonwebtoken.Jws;
|
||||
import io.jsonwebtoken.JwsHeader;
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import io.jsonwebtoken.JwtParser;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.MalformedJwtException;
|
||||
import io.jsonwebtoken.MissingClaimException;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.SigningKeyResolverAdapter;
|
||||
import io.jsonwebtoken.UnsupportedJwtException;
|
||||
|
@ -44,6 +46,7 @@ import org.apache.archiva.redback.authentication.StringToken;
|
|||
import org.apache.archiva.redback.authentication.Token;
|
||||
import org.apache.archiva.redback.authentication.TokenBasedAuthenticationDataSource;
|
||||
import org.apache.archiva.redback.authentication.TokenData;
|
||||
import org.apache.archiva.redback.authentication.TokenType;
|
||||
import org.apache.archiva.redback.configuration.UserConfiguration;
|
||||
import org.apache.archiva.redback.configuration.UserConfigurationKeys;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -78,9 +81,11 @@ import java.time.Instant;
|
|||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
@ -140,7 +145,10 @@ public class JwtAuthenticator extends AbstractAuthenticator implements Authentic
|
|||
{
|
||||
private static final Logger log = LoggerFactory.getLogger( JwtAuthenticator.class );
|
||||
|
||||
// 4 hours for standard tokens
|
||||
public static final String DEFAULT_LIFETIME = "14400000";
|
||||
// 7 days for refresh tokens
|
||||
public static final String DEFAULT_REFRESH_LIFETIME = "604800000";
|
||||
public static final String DEFAULT_KEYFILE = "jwt-key.xml";
|
||||
public static final String ID = "JwtAuthenticator";
|
||||
public static final String PROP_PRIV_ALG = "privateAlgorithm";
|
||||
|
@ -151,6 +159,7 @@ public class JwtAuthenticator extends AbstractAuthenticator implements Authentic
|
|||
public static final String PROP_PUBLICKEY = "publicKey";
|
||||
public static final String PROP_KEYID = "keyId";
|
||||
private static final String ISSUER = "archiva.apache.org/redback";
|
||||
private static final String TOKEN_TYPE = "token_type";
|
||||
|
||||
|
||||
@Inject
|
||||
|
@ -168,9 +177,14 @@ public class JwtAuthenticator extends AbstractAuthenticator implements Authentic
|
|||
AtomicLong keyCounter;
|
||||
final SigningKeyResolver resolver = new SigningKeyResolver( );
|
||||
final ReadWriteLock lock = new ReentrantReadWriteLock( );
|
||||
private JwtParser parser;
|
||||
private Duration lifetime;
|
||||
private Duration tokenLifetime;
|
||||
private Duration refreshTokenLifetime;
|
||||
private Map<TokenType, JwtParser> parserMap = new HashMap<>( );
|
||||
|
||||
|
||||
private JwtParser getParser(TokenType type) {
|
||||
return parserMap.get( type );
|
||||
}
|
||||
public class SigningKeyResolver extends SigningKeyResolverAdapter
|
||||
{
|
||||
|
||||
|
@ -242,12 +256,24 @@ public class JwtAuthenticator extends AbstractAuthenticator implements Authentic
|
|||
// In memory key store is the default
|
||||
addNewKey( );
|
||||
}
|
||||
this.parser = Jwts.parserBuilder( )
|
||||
this.parserMap.put(TokenType.ALL, Jwts.parserBuilder( )
|
||||
.setSigningKeyResolver( getResolver( ) )
|
||||
.requireIssuer( ISSUER )
|
||||
.build( );
|
||||
.build( ));
|
||||
this.parserMap.put(TokenType.ACCESS_TOKEN, Jwts.parserBuilder( )
|
||||
.setSigningKeyResolver( getResolver( ) )
|
||||
.requireIssuer( ISSUER )
|
||||
.require( TOKEN_TYPE, TokenType.ACCESS_TOKEN.getClaim() )
|
||||
.build( ));
|
||||
this.parserMap.put(TokenType.REFRESH_TOKEN, Jwts.parserBuilder( )
|
||||
.setSigningKeyResolver( getResolver( ) )
|
||||
.requireIssuer( ISSUER )
|
||||
.require( TOKEN_TYPE, TokenType.REFRESH_TOKEN.getClaim() )
|
||||
.build( ));
|
||||
|
||||
lifetime = Duration.ofMillis( Long.parseLong( userConfiguration.getString( AUTHENTICATION_JWT_LIFETIME_MS, DEFAULT_LIFETIME ) ) );
|
||||
|
||||
tokenLifetime = Duration.ofMillis( Long.parseLong( userConfiguration.getString( AUTHENTICATION_JWT_LIFETIME_MS, DEFAULT_LIFETIME ) ) );
|
||||
refreshTokenLifetime = Duration.ofMillis( Long.parseLong( userConfiguration.getString( AUTHENTICATION_JWT_REFRESH_LIFETIME_MS, DEFAULT_REFRESH_LIFETIME ) ) );
|
||||
}
|
||||
|
||||
private void addNewSecretKey( Long id, SecretKey key )
|
||||
|
@ -661,33 +687,94 @@ public class JwtAuthenticator extends AbstractAuthenticator implements Authentic
|
|||
{
|
||||
final KeyHolder signerKey = getSignerKey( );
|
||||
Instant now = Instant.now( );
|
||||
Instant expiration = now.plus( lifetime );
|
||||
Instant expiration = now.plus( tokenLifetime );
|
||||
final String token = Jwts.builder( )
|
||||
.setSubject( userId )
|
||||
.setIssuer( ISSUER )
|
||||
.claim( TOKEN_TYPE, TokenType.ACCESS_TOKEN.getClaim( ) )
|
||||
.setIssuedAt( Date.from( now ) )
|
||||
.setExpiration( Date.from( expiration ) )
|
||||
.setHeaderParam( JwsHeader.KEY_ID, signerKey.getId( ).toString( ) )
|
||||
.signWith( signerKey.getSignerKey( ) ).compact( );
|
||||
TokenData metadata = new SimpleTokenData( userId, lifetime.toMillis( ), 0 );
|
||||
return new StringToken( token, metadata );
|
||||
TokenData metadata = new SimpleTokenData( userId, tokenLifetime, 0 );
|
||||
return new StringToken("", token, metadata );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a token for the given user id. The token contains the following data:
|
||||
* <ul>
|
||||
* <li>the userid as subject</li>
|
||||
* <li>a issuer archiva.apache.org/redback</li>
|
||||
* <li>a id header with the key id</li>
|
||||
* </ul>the user id as subject.
|
||||
*
|
||||
* @param userId the user identifier to set as subject
|
||||
* @param type the token type that indicates if this token is a access or refresh token
|
||||
* @return the token string
|
||||
*/
|
||||
public Token generateToken( String userId, TokenType type )
|
||||
{
|
||||
if (type==TokenType.ACCESS_TOKEN) {
|
||||
return generateToken( userId );
|
||||
} else if (type == TokenType.REFRESH_TOKEN)
|
||||
{
|
||||
return generateRefreshToken( userId );
|
||||
} else {
|
||||
throw new RuntimeException( "Invalid token type requested" );
|
||||
}
|
||||
}
|
||||
|
||||
private Token generateRefreshToken(String userId) {
|
||||
final KeyHolder signerKey = getSignerKey( );
|
||||
Instant now = Instant.now( );
|
||||
Instant expiration = now.plus( refreshTokenLifetime );
|
||||
final String id = UUID.randomUUID( ).toString( );
|
||||
final String token = Jwts.builder( )
|
||||
.setSubject( userId )
|
||||
.setIssuer( ISSUER )
|
||||
.setIssuedAt( Date.from( now ) )
|
||||
.setId( id )
|
||||
.claim( TOKEN_TYPE, TokenType.REFRESH_TOKEN.getClaim() )
|
||||
.setExpiration( Date.from( expiration ) )
|
||||
.setHeaderParam( JwsHeader.KEY_ID, signerKey.getId( ).toString( ) )
|
||||
.signWith( signerKey.getSignerKey( ) ).compact( );
|
||||
TokenData metadata = new SimpleTokenData( userId, refreshTokenLifetime, 0 );
|
||||
return new StringToken( TokenType.REFRESH_TOKEN, id, token, metadata );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a token object from the given token String
|
||||
*
|
||||
* @param tokenData the string representation of the token
|
||||
* @return the token instance
|
||||
*/
|
||||
public Token tokenFromString(String tokenData) {
|
||||
Jws<Claims> parsedToken = parseToken( tokenData );
|
||||
String userId = parsedToken.getBody( ).getSubject( );
|
||||
TokenType type = TokenType.ofClaim( parsedToken.getBody( ).get( TOKEN_TYPE, String.class ) );
|
||||
String id = parsedToken.getBody( ).getId( );
|
||||
Instant expiration = parsedToken.getBody( ).getExpiration( ).toInstant( );
|
||||
Instant issuedAt = parsedToken.getBody( ).getIssuedAt( ).toInstant( );
|
||||
long lifetime = Duration.between( issuedAt, expiration ).toMillis( );
|
||||
TokenData metadata = new SimpleTokenData( userId, lifetime, 0 );
|
||||
return new StringToken( type, id, tokenData, metadata );
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to renew a token based on the origin token. If the presented <code>origin</code>
|
||||
* is valid, a new token with refreshed expiration time will be returned.
|
||||
*
|
||||
* @param origin the origin token
|
||||
* @param refreshToken the refresh token
|
||||
* @return the newly created token
|
||||
* @throws AuthenticationException if the given origin token is not valid
|
||||
*/
|
||||
public Token renewToken(String origin) throws AuthenticationException {
|
||||
public Token refreshAccessToken( String refreshToken) throws TokenAuthenticationException {
|
||||
try
|
||||
{
|
||||
Jws<Claims> signature = this.parser.parseClaimsJws( origin );
|
||||
return generateToken( signature.getBody( ).getSubject( ) );
|
||||
} catch (JwtException e) {
|
||||
throw new AuthenticationException( "Could not renew the token " + e.getMessage( ) );
|
||||
String subject = verify( refreshToken, TokenType.REFRESH_TOKEN );
|
||||
return generateToken( subject );
|
||||
} catch ( JwtException e) {
|
||||
throw new TokenAuthenticationException( BearerError.INVALID_TOKEN, "unknown error " + e.getMessage( ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -699,21 +786,26 @@ public class JwtAuthenticator extends AbstractAuthenticator implements Authentic
|
|||
* @throws JwtException if the token data is not valid anymore
|
||||
*/
|
||||
public Jws<Claims> parseToken( String token) throws JwtException {
|
||||
return parser.parseClaimsJws( token );
|
||||
return getParser(TokenType.ALL).parseClaimsJws( token );
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the given JWT Token and returns the stored subject, if successful
|
||||
* If the verification failed a AuthenticationException is thrown.
|
||||
* If the verification failed a TokenAuthenticationException is thrown.
|
||||
* @param token the JWT representation
|
||||
* @return the subject of the JWT
|
||||
* @throws AuthenticationException if the verification failed
|
||||
* @throws TokenAuthenticationException if the verification failed
|
||||
*/
|
||||
public String verify( String token ) throws TokenAuthenticationException
|
||||
{
|
||||
return verify( token, TokenType.ACCESS_TOKEN );
|
||||
}
|
||||
|
||||
public String verify( String token, TokenType type ) throws TokenAuthenticationException
|
||||
{
|
||||
try
|
||||
{
|
||||
Jws<Claims> signature = this.parser.parseClaimsJws( token );
|
||||
Jws<Claims> signature = getParser(type).parseClaimsJws( token );
|
||||
String subject = signature.getBody( ).getSubject( );
|
||||
if ( StringUtils.isEmpty( subject ) )
|
||||
{
|
||||
|
@ -738,6 +830,9 @@ public class JwtAuthenticator extends AbstractAuthenticator implements Authentic
|
|||
catch (JwtKeyIdNotFoundException e) {
|
||||
throw new TokenAuthenticationException( BearerError.INVALID_TOKEN, "signer key does not exist" );
|
||||
}
|
||||
catch ( MissingClaimException |IncorrectClaimException e ) {
|
||||
throw new TokenAuthenticationException( BearerError.INVALID_TOKEN, "the token type is not correct - expected claim "+type.getClaim() );
|
||||
}
|
||||
catch ( JwtException e) {
|
||||
log.debug( "Unknown JwtException {}, {}", e.getClass( ), e.getMessage( ) );
|
||||
throw new TokenAuthenticationException( BearerError.INVALID_TOKEN, "unknown error " + e.getMessage( ) );
|
||||
|
@ -836,7 +931,7 @@ public class JwtAuthenticator extends AbstractAuthenticator implements Authentic
|
|||
* @return the lifetime as duration
|
||||
*/
|
||||
public Duration getTokenLifetime() {
|
||||
return this.lifetime;
|
||||
return this.tokenLifetime;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -844,7 +939,7 @@ public class JwtAuthenticator extends AbstractAuthenticator implements Authentic
|
|||
* @param lifetime the lifetime as duration
|
||||
*/
|
||||
public void setTokenLifetime(Duration lifetime) {
|
||||
this.lifetime = lifetime;
|
||||
this.tokenLifetime = lifetime;
|
||||
}
|
||||
|
||||
public UserConfiguration getUserConfiguration( )
|
||||
|
|
|
@ -29,6 +29,7 @@ import org.apache.archiva.redback.authentication.BearerTokenAuthenticationDataSo
|
|||
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.authentication.TokenType;
|
||||
import org.apache.archiva.redback.configuration.DefaultUserConfiguration;
|
||||
import org.apache.archiva.redback.configuration.UserConfigurationException;
|
||||
import org.apache.commons.configuration2.BaseConfiguration;
|
||||
|
@ -39,6 +40,7 @@ import org.junit.jupiter.api.Test;
|
|||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
|
@ -119,12 +121,6 @@ public abstract class AbstractJwtTest
|
|||
assertTrue( Instant.now( ).isBefore( token.getMetadata( ).validBefore( ) ) );
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void authenticate( )
|
||||
{
|
||||
}
|
||||
|
||||
@Test
|
||||
void renewSigningKey( )
|
||||
{
|
||||
|
@ -231,5 +227,32 @@ public abstract class AbstractJwtTest
|
|||
assertFalse( result.isAuthenticated( ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
void refreshToken() throws TokenAuthenticationException
|
||||
{
|
||||
Token token = jwtAuthenticator.generateToken( "bilbo_baggins" , TokenType.REFRESH_TOKEN);
|
||||
assertNotNull( token );
|
||||
assertTrue( token.getType( ).equals( TokenType.REFRESH_TOKEN ) );
|
||||
UUID tokenId = UUID.fromString( token.getId( ) );
|
||||
assertNotNull( tokenId );
|
||||
Token accessToken = jwtAuthenticator.refreshAccessToken( token.getData() );
|
||||
assertNotNull( accessToken );
|
||||
assertTrue( accessToken.getType( ).equals( TokenType.ACCESS_TOKEN ) );
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidRefreshWithAccessToken() throws TokenAuthenticationException
|
||||
{
|
||||
Token token = jwtAuthenticator.generateToken( "bilbo_baggins");
|
||||
assertNotNull( token );
|
||||
assertTrue( token.getType( ).equals( TokenType.ACCESS_TOKEN ) );
|
||||
TokenAuthenticationException thrownException = assertThrows( TokenAuthenticationException.class, ( ) -> {
|
||||
jwtAuthenticator.refreshAccessToken( token.getData( ) );
|
||||
} );
|
||||
assertEquals( BearerError.INVALID_TOKEN, thrownException.getError( ) );
|
||||
assertTrue( thrownException.getMessage( ).contains( "the token type is not correct - expected claim " + TokenType.REFRESH_TOKEN.getClaim( ) ) );
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -187,25 +187,25 @@ public interface UserConfigurationKeys
|
|||
String MAIL_DEFAULT_LOCALE = "mail.locale";
|
||||
|
||||
/**
|
||||
* Defines, where the key for JWT encryption / decryption is stored.
|
||||
* The property for defining, where the key for JWT encryption / decryption is stored.
|
||||
* Currently only memory and plainfile are supported
|
||||
* {@value}
|
||||
*/
|
||||
String AUTHENTICATION_JWT_KEYSTORETYPE = "authentication.jwt.keystoreType";
|
||||
/**
|
||||
* Keystore type name for memory keystore: {@value}
|
||||
* The property value for memory keystore: {@value}
|
||||
*/
|
||||
String AUTHENTICATION_JWT_KEYSTORETYPE_MEMORY = "memory";
|
||||
/**
|
||||
* Keystore type name for plain file keystore: {@value}
|
||||
* The property value for plain file keystore: {@value}
|
||||
*/
|
||||
String AUTHENTICATION_JWT_KEYSTORETYPE_PLAINFILE = "plainfile";
|
||||
/**
|
||||
* Defines the used signature algorithm for JWT signing: {@value}
|
||||
* The property for defining the used signature algorithm for JWT signing: {@value}
|
||||
*/
|
||||
String AUTHENTICATION_JWT_SIGALG = "authentication.jwt.signatureAlgorithm";
|
||||
/**
|
||||
* Defines the maximum number of keys to keep in memory for verificatio: {@value}
|
||||
* The property for defining the maximum number of keys to keep in memory for verification: {@value}
|
||||
*/
|
||||
String AUTHENTICATION_JWT_MAX_KEYS = "authentication.jwt.maxInMemoryKeys";
|
||||
|
||||
|
@ -260,13 +260,18 @@ public interface UserConfigurationKeys
|
|||
|
||||
|
||||
/**
|
||||
* Path to the file where the JWT key is stored: {@value}
|
||||
* The property for the path to the file where the JWT key is stored: {@value}
|
||||
*/
|
||||
String AUTHENTICATION_JWT_KEYFILE = "authentication.jwt.keyfile";
|
||||
|
||||
/**
|
||||
* The lifetime in ms of the generated tokens: {@value}
|
||||
* The property for lifetime in ms of the generated tokens: {@value}
|
||||
*/
|
||||
String AUTHENTICATION_JWT_LIFETIME_MS = "authentication.jwt.lifetimeMs";
|
||||
|
||||
/**
|
||||
* The property for lifetime in ms of the generated refresh tokens: {@value}
|
||||
*/
|
||||
String AUTHENTICATION_JWT_REFRESH_LIFETIME_MS = "authentication.jwt.refreshLifetimeMs";
|
||||
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.apache.archiva.redback.rest.api.model;
|
|||
*/
|
||||
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
/**
|
||||
* @author Martin Stockhammer <martin_s@apache.org>
|
||||
|
@ -27,13 +28,15 @@ import javax.xml.bind.annotation.XmlRootElement;
|
|||
public class PingResult
|
||||
{
|
||||
boolean success;
|
||||
OffsetDateTime requestTime;
|
||||
|
||||
public PingResult() {
|
||||
|
||||
this.requestTime = OffsetDateTime.now( );
|
||||
}
|
||||
|
||||
public PingResult( boolean success ) {
|
||||
this.success = success;
|
||||
this.requestTime = OffsetDateTime.now( );
|
||||
}
|
||||
|
||||
public boolean isSuccess( )
|
||||
|
@ -45,4 +48,14 @@ public class PingResult
|
|||
{
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
public OffsetDateTime getRequestTime( )
|
||||
{
|
||||
return requestTime;
|
||||
}
|
||||
|
||||
public void setRequestTime( OffsetDateTime requestTime )
|
||||
{
|
||||
this.requestTime = requestTime;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
package org.apache.archiva.redback.rest.api.model;
|
||||
|
||||
/*
|
||||
* 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 javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
|
||||
/**
|
||||
* @author Martin Stockhammer <martin_s@apache.org>
|
||||
*/
|
||||
@XmlRootElement(name="refreshToken")
|
||||
public class RefreshTokenRequest
|
||||
{
|
||||
String grantType;
|
||||
String refreshToken;
|
||||
String scope;
|
||||
|
||||
public RefreshTokenRequest( )
|
||||
{
|
||||
}
|
||||
|
||||
public RefreshTokenRequest( String grantType, String refreshToken, String scope )
|
||||
{
|
||||
this.grantType = grantType;
|
||||
this.refreshToken = refreshToken;
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
@XmlElement(name = "grant_type")
|
||||
public String getGrantType( )
|
||||
{
|
||||
return grantType;
|
||||
}
|
||||
|
||||
public void setGrantType( String grantType )
|
||||
{
|
||||
this.grantType = grantType;
|
||||
}
|
||||
|
||||
@XmlElement(name="refresh_token")
|
||||
public String getRefreshToken( )
|
||||
{
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
public void setRefreshToken( String refreshToken )
|
||||
{
|
||||
this.refreshToken = refreshToken;
|
||||
}
|
||||
|
||||
@XmlElement(name="scope")
|
||||
public String getScope( )
|
||||
{
|
||||
return scope;
|
||||
}
|
||||
|
||||
public void setScope( String scope )
|
||||
{
|
||||
this.scope = scope;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
package org.apache.archiva.redback.rest.api.model;
|
||||
|
||||
/*
|
||||
* 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 javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
|
||||
/**
|
||||
* @author Martin Stockhammer <martin_s@apache.org>
|
||||
*/
|
||||
@XmlRootElement(name="refreshToken")
|
||||
public class RequestTokenRequest
|
||||
{
|
||||
String grantType = "authorization_code";
|
||||
String clientId;
|
||||
String clientSecret;
|
||||
String code;
|
||||
String scope = "";
|
||||
String state = "";
|
||||
String userId;
|
||||
String password;
|
||||
String redirectUri;
|
||||
|
||||
public RequestTokenRequest() {
|
||||
|
||||
}
|
||||
|
||||
public RequestTokenRequest( String userId, String password )
|
||||
{
|
||||
this.userId = userId;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public RequestTokenRequest( String userId, String password, String scope )
|
||||
{
|
||||
this.userId = userId;
|
||||
this.password = password;
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
@XmlElement(name = "grant_type", required = true, nillable = false)
|
||||
public String getGrantType( )
|
||||
{
|
||||
return grantType;
|
||||
}
|
||||
|
||||
public void setGrantType( String grantType )
|
||||
{
|
||||
this.grantType = grantType;
|
||||
}
|
||||
|
||||
@XmlElement(name="client_id", required = false, nillable = true)
|
||||
public String getClientId( )
|
||||
{
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public void setClientId( String clientId )
|
||||
{
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
@XmlElement(name="client_secret", required = false, nillable = true)
|
||||
public String getClientSecret( )
|
||||
{
|
||||
return clientSecret;
|
||||
}
|
||||
|
||||
public void setClientSecret( String clientSecret )
|
||||
{
|
||||
this.clientSecret = clientSecret;
|
||||
}
|
||||
|
||||
@XmlElement(name="scope", required = false, nillable = true)
|
||||
public String getScope( )
|
||||
{
|
||||
return scope;
|
||||
}
|
||||
|
||||
public void setScope( String scope )
|
||||
{
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
@XmlElement(name="user_id", required = true, nillable = false)
|
||||
public String getUserId( )
|
||||
{
|
||||
return userId;
|
||||
}
|
||||
|
||||
@XmlElement(name="user_id", required = true, nillable = false)
|
||||
public void setUserId( String userId )
|
||||
{
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
@XmlElement(name="password", required = true, nillable = false)
|
||||
public String getPassword( )
|
||||
{
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword( String password )
|
||||
{
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
@XmlElement(name="code", required = false, nillable = false)
|
||||
public String getCode( )
|
||||
{
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode( String code )
|
||||
{
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
@XmlElement(name="redirect_uri", required = false, nillable = false)
|
||||
public String getRedirectUri( )
|
||||
{
|
||||
return redirectUri;
|
||||
}
|
||||
|
||||
public void setRedirectUri( String redirectUri )
|
||||
{
|
||||
this.redirectUri = redirectUri;
|
||||
}
|
||||
|
||||
@XmlElement(name="state", required = false, nillable = false)
|
||||
public String getState( )
|
||||
{
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState( String state )
|
||||
{
|
||||
this.state = state;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
package org.apache.archiva.redback.rest.api.model;
|
||||
|
||||
/*
|
||||
* 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.Token;
|
||||
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* @author Martin Stockhammer <martin_s@apache.org>
|
||||
*/
|
||||
@XmlRootElement(name="token")
|
||||
public class TokenResponse
|
||||
{
|
||||
String accessToken;
|
||||
String tokenType = "bearer";
|
||||
long expiresIn;
|
||||
String refreshToken;
|
||||
String scope;
|
||||
String state;
|
||||
|
||||
public TokenResponse( )
|
||||
{
|
||||
}
|
||||
|
||||
public TokenResponse( String accessToken, String tokenType, long expiresIn, String refreshToken, String scope )
|
||||
{
|
||||
this.accessToken = accessToken;
|
||||
this.tokenType = tokenType;
|
||||
this.expiresIn = expiresIn;
|
||||
this.refreshToken = refreshToken;
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
public TokenResponse( String accessToken, long expiresIn, String refreshToken, String scope )
|
||||
{
|
||||
this.accessToken = accessToken;
|
||||
this.expiresIn = expiresIn;
|
||||
this.refreshToken = refreshToken;
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
public TokenResponse( Token accessToken, Token refreshToken )
|
||||
{
|
||||
this.expiresIn = Duration.between( Instant.now( ), accessToken.getMetadata( ).validBefore( ) ).getSeconds();
|
||||
this.accessToken = accessToken.getData( );
|
||||
this.refreshToken = refreshToken.getData( );
|
||||
this.scope = "";
|
||||
}
|
||||
|
||||
public TokenResponse( Token accessToken, Token refreshToken , String scope, String state)
|
||||
{
|
||||
this.expiresIn = Duration.between( Instant.now( ), accessToken.getMetadata( ).validBefore( ) ).getSeconds();
|
||||
this.accessToken = accessToken.getData( );
|
||||
this.refreshToken = refreshToken.getData( );
|
||||
this.scope = scope;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
@XmlElement(name="access_token")
|
||||
public String getAccessToken( )
|
||||
{
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public void setAccessToken( String accessToken )
|
||||
{
|
||||
this.accessToken = accessToken;
|
||||
}
|
||||
|
||||
@XmlElement(name="token_type")
|
||||
public String getTokenType( )
|
||||
{
|
||||
return tokenType;
|
||||
}
|
||||
|
||||
public void setTokenType( String tokenType )
|
||||
{
|
||||
this.tokenType = tokenType;
|
||||
}
|
||||
|
||||
@XmlElement(name="expires_in")
|
||||
public long getExpiresIn( )
|
||||
{
|
||||
return expiresIn;
|
||||
}
|
||||
|
||||
public void setExpiresIn( long expiresIn )
|
||||
{
|
||||
this.expiresIn = expiresIn;
|
||||
}
|
||||
|
||||
@XmlElement(name="refresh_token")
|
||||
public String getRefreshToken( )
|
||||
{
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
public void setRefreshToken( String refreshToken )
|
||||
{
|
||||
this.refreshToken = refreshToken;
|
||||
}
|
||||
|
||||
public String getScope( )
|
||||
{
|
||||
return scope;
|
||||
}
|
||||
|
||||
public void setScope( String scope )
|
||||
{
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
public String getState( )
|
||||
{
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState( String state )
|
||||
{
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public boolean hasState() {
|
||||
return state != null && state.length( ) > 0;
|
||||
}
|
||||
}
|
|
@ -56,7 +56,7 @@ public interface LoginService
|
|||
@GET
|
||||
@Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
|
||||
@RedbackAuthorization( noRestriction = true )
|
||||
PingResult ping()
|
||||
Boolean ping()
|
||||
throws RedbackServiceException;
|
||||
|
||||
|
||||
|
@ -65,7 +65,7 @@ public interface LoginService
|
|||
@GET
|
||||
@Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
|
||||
@RedbackAuthorization( noRestriction = false, noPermission = true )
|
||||
PingResult pingWithAutz()
|
||||
Boolean pingWithAutz()
|
||||
throws RedbackServiceException;
|
||||
|
||||
/**
|
||||
|
|
|
@ -151,7 +151,7 @@ public interface UserService
|
|||
@GET
|
||||
@Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
|
||||
@RedbackAuthorization( noRestriction = true )
|
||||
PingResult ping()
|
||||
Boolean ping()
|
||||
throws RedbackServiceException;
|
||||
|
||||
@Path( "removeFromCache/{userName}" )
|
||||
|
|
|
@ -25,7 +25,10 @@ import org.apache.archiva.redback.authorization.RedbackAuthorization;
|
|||
import org.apache.archiva.redback.rest.api.model.ActionStatus;
|
||||
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.RefreshTokenRequest;
|
||||
import org.apache.archiva.redback.rest.api.model.RequestTokenRequest;
|
||||
import org.apache.archiva.redback.rest.api.model.Token;
|
||||
import org.apache.archiva.redback.rest.api.model.TokenResponse;
|
||||
import org.apache.archiva.redback.rest.api.model.User;
|
||||
import org.apache.archiva.redback.rest.api.services.RedbackServiceException;
|
||||
|
||||
|
@ -43,17 +46,6 @@ import javax.ws.rs.core.MediaType;
|
|||
public interface AuthenticationService
|
||||
{
|
||||
|
||||
@Path( "requestkey" )
|
||||
@GET
|
||||
@Produces( { MediaType.APPLICATION_JSON } )
|
||||
@RedbackAuthorization( noRestriction = true )
|
||||
Token requestOnetimeToken( @QueryParam( "providerKey" ) String providedKey,
|
||||
@QueryParam( "principal" ) String principal,
|
||||
@QueryParam( "purpose" ) String purpose,
|
||||
@QueryParam( "expirationSeconds" ) int expirationSeconds )
|
||||
throws RedbackServiceException;
|
||||
|
||||
|
||||
@Path( "ping" )
|
||||
@GET
|
||||
@Produces( { MediaType.APPLICATION_JSON } )
|
||||
|
@ -74,7 +66,7 @@ public interface AuthenticationService
|
|||
* The bearer token can be added to the HTTP header on further requests to authenticate.
|
||||
*
|
||||
*/
|
||||
@Path( "authenticate" )
|
||||
@Path( "token" )
|
||||
@POST
|
||||
@RedbackAuthorization( noRestriction = true, noPermission = true )
|
||||
@Produces( { MediaType.APPLICATION_JSON } )
|
||||
|
@ -83,23 +75,23 @@ public interface AuthenticationService
|
|||
@ApiResponse( description = "The bearer token. The token data contains the token string that should be added to the Bearer header" )
|
||||
}
|
||||
)
|
||||
Token logIn( LoginRequest loginRequest )
|
||||
TokenResponse logIn( RequestTokenRequest loginRequest )
|
||||
throws RedbackServiceException;
|
||||
|
||||
/**
|
||||
* Renew the bearer token. The request must send a bearer token in the HTTP header
|
||||
*
|
||||
*/
|
||||
@Path( "authenticate" )
|
||||
@GET
|
||||
@Path( "refresh" )
|
||||
@POST
|
||||
@RedbackAuthorization( noRestriction = false, noPermission = true )
|
||||
@Produces( { MediaType.APPLICATION_JSON } )
|
||||
@Operation( summary = "Creates a new bearer token. The requestor must present a still valid bearer token in the HTTP header.",
|
||||
@Operation( summary = "Creates a new bearer token. The requester must present a still valid bearer token in the HTTP header.",
|
||||
responses = {
|
||||
@ApiResponse( description = "The new bearer token," )
|
||||
}
|
||||
)
|
||||
Token renewToken( )
|
||||
TokenResponse refreshToken( RefreshTokenRequest refreshTokenRequest )
|
||||
throws RedbackServiceException;
|
||||
|
||||
|
||||
|
@ -107,21 +99,11 @@ public interface AuthenticationService
|
|||
* simply check if current user has an http session opened with authz passed and return user data
|
||||
* @since 1.4
|
||||
*/
|
||||
@Path( "isAuthenticated" )
|
||||
@Path( "authenticated" )
|
||||
@GET
|
||||
@Produces( { MediaType.APPLICATION_JSON } )
|
||||
@RedbackAuthorization( noRestriction = true )
|
||||
User isLogged()
|
||||
User getAuthenticatedUser()
|
||||
throws RedbackServiceException;
|
||||
|
||||
/**
|
||||
* clear user http session
|
||||
* @since 1.4
|
||||
*/
|
||||
@Path( "logout" )
|
||||
@GET
|
||||
@Produces( { MediaType.APPLICATION_JSON } )
|
||||
@RedbackAuthorization( noRestriction = true, noPermission = true )
|
||||
ActionStatus logout()
|
||||
throws RedbackServiceException;
|
||||
}
|
||||
|
|
|
@ -126,16 +126,16 @@ public class DefaultLoginService
|
|||
return key.getKey( );
|
||||
}
|
||||
|
||||
public PingResult ping()
|
||||
public Boolean ping()
|
||||
throws RedbackServiceException
|
||||
{
|
||||
return new PingResult( true);
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
public PingResult pingWithAutz()
|
||||
public Boolean pingWithAutz()
|
||||
throws RedbackServiceException
|
||||
{
|
||||
return new PingResult( true );
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
public User logIn( LoginRequest loginRequest )
|
||||
|
|
|
@ -490,10 +490,10 @@ public class DefaultUserService
|
|||
}
|
||||
|
||||
@Override
|
||||
public PingResult ping()
|
||||
public Boolean ping()
|
||||
throws RedbackServiceException
|
||||
{
|
||||
return new PingResult( true );
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
private User getSimpleUser( org.apache.archiva.redback.users.User user )
|
||||
|
|
|
@ -23,20 +23,18 @@ 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.PasswordBasedAuthenticationDataSource;
|
||||
import org.apache.archiva.redback.authentication.Token;
|
||||
import org.apache.archiva.redback.authentication.TokenType;
|
||||
import org.apache.archiva.redback.authentication.jwt.JwtAuthenticator;
|
||||
import org.apache.archiva.redback.authentication.jwt.TokenAuthenticationException;
|
||||
import org.apache.archiva.redback.integration.filter.authentication.HttpAuthenticator;
|
||||
import org.apache.archiva.redback.keys.AuthenticationKey;
|
||||
import org.apache.archiva.redback.keys.KeyManager;
|
||||
import org.apache.archiva.redback.keys.jpa.model.JpaAuthenticationKey;
|
||||
import org.apache.archiva.redback.keys.memory.MemoryAuthenticationKey;
|
||||
import org.apache.archiva.redback.keys.memory.MemoryKeyManager;
|
||||
import org.apache.archiva.redback.policy.AccountLockedException;
|
||||
import org.apache.archiva.redback.policy.MustChangePasswordException;
|
||||
import org.apache.archiva.redback.rest.api.model.ActionStatus;
|
||||
import org.apache.archiva.redback.rest.api.model.ErrorMessage;
|
||||
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.RefreshTokenRequest;
|
||||
import org.apache.archiva.redback.rest.api.model.RequestTokenRequest;
|
||||
import org.apache.archiva.redback.rest.api.model.TokenResponse;
|
||||
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;
|
||||
|
@ -53,17 +51,11 @@ 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.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -105,37 +97,6 @@ public class DefaultAuthenticationService
|
|||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Token requestOnetimeToken( String providedKey, String principal, String purpose, int expirationSeconds )
|
||||
{
|
||||
KeyManager keyManager = securitySystem.getKeyManager();
|
||||
AuthenticationKey key;
|
||||
|
||||
if ( keyManager instanceof MemoryKeyManager )
|
||||
{
|
||||
key = new MemoryAuthenticationKey();
|
||||
}
|
||||
else
|
||||
{
|
||||
key = new JpaAuthenticationKey();
|
||||
}
|
||||
|
||||
key.setKey( providedKey );
|
||||
key.setForPrincipal( principal );
|
||||
key.setPurpose( purpose );
|
||||
|
||||
Instant now = Instant.now( );
|
||||
key.setDateCreated( Date.from( now ) );
|
||||
|
||||
if ( expirationSeconds >= 0 )
|
||||
{
|
||||
Duration expireDuration = Duration.ofSeconds( expirationSeconds );
|
||||
key.setDateExpires( Date.from( now.plus( expireDuration ) ) );
|
||||
}
|
||||
keyManager.addKey( key );
|
||||
return Token.of( key );
|
||||
}
|
||||
|
||||
@Override
|
||||
public PingResult ping()
|
||||
{
|
||||
|
@ -148,15 +109,11 @@ public class DefaultAuthenticationService
|
|||
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 )
|
||||
public TokenResponse logIn( RequestTokenRequest loginRequest )
|
||||
throws RedbackServiceException
|
||||
{
|
||||
String userName = loginRequest.getUsername(), password = loginRequest.getPassword();
|
||||
String userName = loginRequest.getUserId(), password = loginRequest.getPassword();
|
||||
PasswordBasedAuthenticationDataSource authDataSource =
|
||||
new PasswordBasedAuthenticationDataSource( userName, password );
|
||||
log.debug("Login for {}",userName);
|
||||
|
@ -177,8 +134,10 @@ public class DefaultAuthenticationService
|
|||
}
|
||||
// Stateless services no session
|
||||
// httpAuthenticator.authenticate( authDataSource, httpServletRequest.getSession( true ) );
|
||||
Token restToken = getRestToken( token );
|
||||
return restToken;
|
||||
org.apache.archiva.redback.authentication.Token refreshToken = jwtAuthenticator.generateToken( user.getUsername( ), TokenType.REFRESH_TOKEN );
|
||||
response.setHeader( "Cache-Control", "no-store" );
|
||||
response.setHeader( "Pragma", "no-cache" );
|
||||
return new TokenResponse(token, refreshToken, "", loginRequest.getState());
|
||||
} else if ( securitySession.getAuthenticationResult() != null
|
||||
&& securitySession.getAuthenticationResult().getAuthenticationFailureCauses() != null )
|
||||
{
|
||||
|
@ -231,13 +190,25 @@ public class DefaultAuthenticationService
|
|||
}
|
||||
|
||||
@Override
|
||||
public Token renewToken( ) throws RedbackServiceException
|
||||
public TokenResponse refreshToken( RefreshTokenRequest request ) throws RedbackServiceException
|
||||
{
|
||||
return null;
|
||||
if (!"refresh_token".equals(request.getGrantType().toLowerCase())) {
|
||||
throw new RedbackServiceException( "redback:bad_grant", Response.Status.FORBIDDEN.getStatusCode( ) );
|
||||
}
|
||||
try
|
||||
{
|
||||
Token accessToken = jwtAuthenticator.refreshAccessToken( request.getRefreshToken( ) );
|
||||
Token refreshToken = jwtAuthenticator.tokenFromString( request.getRefreshToken( ) );
|
||||
return new TokenResponse( accessToken, refreshToken );
|
||||
}
|
||||
catch ( TokenAuthenticationException e )
|
||||
{
|
||||
throw new RedbackServiceException( e.getError( ).getError( ), Response.Status.UNAUTHORIZED.getStatusCode( ) );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public User isLogged()
|
||||
public User getAuthenticatedUser()
|
||||
throws RedbackServiceException
|
||||
{
|
||||
SecuritySession securitySession = httpAuthenticator.getSecuritySession( httpServletRequest.getSession( true ) );
|
||||
|
@ -246,23 +217,6 @@ public class DefaultAuthenticationService
|
|||
return isLogged && securitySession.getUser() != null ? buildRestUser( securitySession.getUser() ) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionStatus logout()
|
||||
throws RedbackServiceException
|
||||
{
|
||||
HttpSession httpSession = httpServletRequest.getSession();
|
||||
if ( httpSession != null )
|
||||
{
|
||||
httpSession.invalidate();
|
||||
}
|
||||
return ActionStatus.SUCCESS;
|
||||
}
|
||||
|
||||
private Calendar getNowGMT()
|
||||
{
|
||||
return Calendar.getInstance( TimeZone.getTimeZone( "GMT" ) );
|
||||
}
|
||||
|
||||
private UserLogin buildRestUser( org.apache.archiva.redback.users.User user )
|
||||
{
|
||||
UserLogin restUser = new UserLogin();
|
||||
|
|
|
@ -44,6 +44,13 @@ public class LoginServiceTest
|
|||
FakeCreateAdminService.ADMIN_TEST_PWD ) ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ping()
|
||||
throws Exception
|
||||
{
|
||||
assertNotNull( getLoginService( null ).ping( ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createUserThenLog()
|
||||
throws Exception
|
||||
|
|
|
@ -57,7 +57,7 @@ public class UserServiceTest
|
|||
public void ping()
|
||||
throws Exception
|
||||
{
|
||||
Boolean res = getUserService().ping().isSuccess();
|
||||
Boolean res = getUserService().ping();
|
||||
assertTrue( res.booleanValue() );
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,289 @@
|
|||
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 io.restassured.RestAssured;
|
||||
import io.restassured.builder.RequestSpecBuilder;
|
||||
import io.restassured.specification.RequestSpecification;
|
||||
import org.apache.archiva.redback.integration.security.role.RedbackRoleConstants;
|
||||
import org.apache.archiva.redback.rest.services.FakeCreateAdminServiceImpl;
|
||||
import org.apache.archiva.redback.role.RoleManager;
|
||||
import org.apache.archiva.redback.role.RoleManagerException;
|
||||
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.apache.commons.lang3.SystemUtils;
|
||||
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.Tag;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.web.context.ContextLoaderListener;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static io.restassured.RestAssured.baseURI;
|
||||
import static io.restassured.RestAssured.port;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
/**
|
||||
*
|
||||
* Native REST tests do not use the JAX-RS client and can be used with a remote
|
||||
* REST API service. The tests
|
||||
*
|
||||
* @author Martin Stockhammer <martin_s@apache.org>
|
||||
*/
|
||||
@Tag("rest-native")
|
||||
public abstract class AbstractNativeRestServices
|
||||
{
|
||||
public static final String SYSPROP_START_SERVER = "archiva.rest.start.server";
|
||||
public static final String SYSPROP_SERVER_PORT = "archiva.rest.server.port";
|
||||
public static final String SYSPROP_SERVER_BASE_URI = "archiva.rest.server.baseuri";
|
||||
public static final int STOPPED = 0;
|
||||
public static final int STOPPING = 1;
|
||||
public static final int STARTING = 2;
|
||||
public static final int STARTED = 3;
|
||||
public static final int ERROR = 4;
|
||||
|
||||
private RequestSpecification requestSpec;
|
||||
protected Logger log = LoggerFactory.getLogger( getClass() );
|
||||
|
||||
private static AtomicReference<Server> server = new AtomicReference<>();
|
||||
private static AtomicReference<ServerConnector> serverConnector = new AtomicReference<>();
|
||||
private static AtomicInteger serverStarted = new AtomicInteger( STOPPED );
|
||||
private UserManager userManager;
|
||||
private RoleManager roleManager;
|
||||
|
||||
|
||||
protected abstract String getServicePath();
|
||||
|
||||
protected String getSpringConfigLocation()
|
||||
{
|
||||
return "classpath*:spring-context.xml,classpath*:META-INF/spring-context.xml";
|
||||
}
|
||||
|
||||
protected RequestSpecification getRequestSpec() {
|
||||
return this.requestSpec;
|
||||
}
|
||||
|
||||
protected String getContextRoot()
|
||||
{
|
||||
return "/api";
|
||||
}
|
||||
|
||||
|
||||
private String getServiceBasePath( )
|
||||
{
|
||||
return "/v2/redback";
|
||||
}
|
||||
|
||||
protected String getBasePath( )
|
||||
{
|
||||
return new StringBuilder( )
|
||||
.append(getContextRoot( ))
|
||||
.append(getServiceBasePath( ))
|
||||
.append(getServicePath( )).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true, if the server does exist and is running.
|
||||
* @return true, if server does exist and is running.
|
||||
*/
|
||||
public boolean isServerRunning() {
|
||||
return serverStarted.get()==STARTED && this.server.get() != null && this.server.get().isRunning();
|
||||
}
|
||||
|
||||
private UserManager getUserManager() {
|
||||
if (this.userManager==null) {
|
||||
UserManager userManager = ContextLoaderListener.getCurrentWebApplicationContext( )
|
||||
.getBean( "userManager#default", UserManager.class );
|
||||
assertNotNull( userManager );
|
||||
this.userManager = userManager;
|
||||
}
|
||||
return this.userManager;
|
||||
}
|
||||
|
||||
private RoleManager getRoleManager() {
|
||||
if (this.roleManager==null) {
|
||||
RoleManager roleManager = ContextLoaderListener.getCurrentWebApplicationContext( )
|
||||
.getBean( "roleManager", RoleManager.class );
|
||||
assertNotNull( roleManager );
|
||||
this.roleManager = roleManager;
|
||||
}
|
||||
return this.roleManager;
|
||||
}
|
||||
|
||||
private void setupAdminUser() throws UserManagerException, RoleManagerException
|
||||
{
|
||||
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( ) );
|
||||
}
|
||||
}
|
||||
|
||||
public void startServer()
|
||||
throws Exception
|
||||
{
|
||||
if (serverStarted.compareAndSet( STOPPED, STARTING ))
|
||||
{
|
||||
try
|
||||
{
|
||||
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, getContextRoot( ) + "/*" );
|
||||
context.setInitParameter( "contextConfigLocation", getSpringConfigLocation( ) );
|
||||
context.addEventListener( new ContextLoaderListener( ) );
|
||||
|
||||
getServer( ).setHandler( context );
|
||||
getServer( ).start( );
|
||||
|
||||
if ( log.isDebugEnabled( ) )
|
||||
{
|
||||
log.debug( "Jetty dump: {}", getServer( ).dump( ) );
|
||||
}
|
||||
|
||||
setupAdminUser();
|
||||
log.info( "Started server on port {}", getServerPort( ) );
|
||||
serverStarted.set( STARTED );
|
||||
} finally {
|
||||
// In case, if the last statement was not reached
|
||||
serverStarted.compareAndSet( STARTING, ERROR );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void stopServer()
|
||||
throws Exception
|
||||
{
|
||||
if ( this.serverStarted.compareAndSet( STARTED, STOPPING ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
final Server myServer = getServer( );
|
||||
if ( myServer != null )
|
||||
{
|
||||
log.info("Stopping server");
|
||||
myServer.stop();
|
||||
}
|
||||
serverStarted.set( STOPPED );
|
||||
} finally {
|
||||
serverStarted.compareAndSet( STOPPING, ERROR );
|
||||
}
|
||||
} else {
|
||||
log.error( "Serer is not in STARTED state!" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected void setupNative( ) throws Exception
|
||||
{
|
||||
String startServer = System.getProperty( SYSPROP_START_SERVER, "yes" ).toLowerCase( );
|
||||
String serverPort = System.getProperty( SYSPROP_SERVER_PORT, "" );
|
||||
String baseUri = System.getProperty( SYSPROP_SERVER_BASE_URI, "http://localhost" );
|
||||
|
||||
if ( !"no".equals( startServer ) )
|
||||
{
|
||||
startServer( );
|
||||
}
|
||||
|
||||
if ( StringUtils.isNotEmpty( serverPort ) )
|
||||
{
|
||||
RestAssured.port = Integer.parseInt( serverPort );
|
||||
}
|
||||
else
|
||||
{
|
||||
RestAssured.port = getServerPort( );
|
||||
}
|
||||
if ( StringUtils.isNotEmpty( baseUri ) )
|
||||
{
|
||||
RestAssured.baseURI = baseUri;
|
||||
}
|
||||
else
|
||||
{
|
||||
RestAssured.baseURI = "http://localhost";
|
||||
}
|
||||
String basePath = getBasePath( );
|
||||
RequestSpecBuilder builder = new RequestSpecBuilder( );
|
||||
builder.setBaseUri( baseURI )
|
||||
.setPort( port )
|
||||
.setBasePath( basePath )
|
||||
.addHeader( "Origin", RestAssured.baseURI + ":" + RestAssured.port );
|
||||
this.requestSpec = builder.build( );
|
||||
RestAssured.basePath = basePath;
|
||||
}
|
||||
|
||||
protected void shutdownNative() throws Exception
|
||||
{
|
||||
stopServer();
|
||||
}
|
||||
}
|
|
@ -22,10 +22,10 @@ package org.apache.archiva.redback.rest.services.v2;
|
|||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
|
||||
import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector;
|
||||
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;
|
||||
|
@ -56,6 +56,7 @@ import javax.naming.NameNotFoundException;
|
|||
import javax.naming.NamingException;
|
||||
import javax.naming.directory.DirContext;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
|
@ -137,6 +138,8 @@ public abstract class AbstractRestServicesTestV2
|
|||
JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider( );
|
||||
ObjectMapper mapper = new ObjectMapper( );
|
||||
mapper.registerModule( new JavaTimeModule( ) );
|
||||
mapper.setAnnotationIntrospector( new JaxbAnnotationIntrospector( mapper.getTypeFactory() ) );
|
||||
mapper.setDateFormat( new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSSZ" ) );
|
||||
provider.setMapper( mapper );
|
||||
return provider;
|
||||
}
|
||||
|
@ -227,7 +230,7 @@ public abstract class AbstractRestServicesTestV2
|
|||
|
||||
protected String getRestServicesPath()
|
||||
{
|
||||
return "restServices";
|
||||
return "api";
|
||||
}
|
||||
|
||||
public void startServer()
|
||||
|
|
|
@ -20,7 +20,9 @@ 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.RequestTokenRequest;
|
||||
import org.apache.archiva.redback.rest.api.model.Token;
|
||||
import org.apache.archiva.redback.rest.api.model.TokenResponse;
|
||||
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.FakeCreateAdminService;
|
||||
|
@ -64,7 +66,7 @@ public class AuthenticationServiceTest
|
|||
public void loginAdmin()
|
||||
throws Exception
|
||||
{
|
||||
assertNotNull( getLoginServiceV2( null ).logIn( new LoginRequest( RedbackRoleConstants.ADMINISTRATOR_ACCOUNT_NAME,
|
||||
assertNotNull( getLoginServiceV2( null ).logIn( new RequestTokenRequest( RedbackRoleConstants.ADMINISTRATOR_ACCOUNT_NAME,
|
||||
FakeCreateAdminService.ADMIN_TEST_PWD ) ) );
|
||||
}
|
||||
|
||||
|
@ -117,8 +119,8 @@ public class AuthenticationServiceTest
|
|||
user.setPasswordChangeRequired( false );
|
||||
um.updateUser( user );
|
||||
// END SNIPPET: create-user
|
||||
LoginRequest request = new LoginRequest( "toto", "foo123" );
|
||||
Token result = getLoginServiceV2( "" ).logIn( request );
|
||||
RequestTokenRequest request = new RequestTokenRequest( "toto", "foo123" );
|
||||
TokenResponse result = getLoginServiceV2( "" ).logIn( request );
|
||||
// assertNotNull( result );
|
||||
// assertEquals( "toto", result.getUsername( ) );
|
||||
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
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 io.restassured.RestAssured;
|
||||
import io.restassured.builder.RequestSpecBuilder;
|
||||
import io.restassured.response.Response;
|
||||
import io.restassured.specification.RequestSpecification;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
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.junit.jupiter.SpringExtension;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
import static io.restassured.RestAssured.*;
|
||||
import static io.restassured.http.ContentType.JSON;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.springframework.core.annotation.MergedAnnotations.from;
|
||||
|
||||
/**
|
||||
* @author Martin Stockhammer <martin_s@apache.org>
|
||||
*/
|
||||
@ExtendWith( SpringExtension.class )
|
||||
@ContextConfiguration(
|
||||
locations = {"classpath:/ldap-spring-test.xml"} )
|
||||
@TestInstance( TestInstance.Lifecycle.PER_CLASS )
|
||||
@Tag( "rest-native" )
|
||||
public class NativeAuthenticationServiceTest extends AbstractNativeRestServices
|
||||
{
|
||||
|
||||
@Override
|
||||
protected String getServicePath( )
|
||||
{
|
||||
return "/auth";
|
||||
}
|
||||
|
||||
@BeforeAll
|
||||
void setup( ) throws Exception
|
||||
{
|
||||
setupNative( );
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
void shutdown( ) throws Exception
|
||||
{
|
||||
shutdownNative();
|
||||
}
|
||||
|
||||
@Test
|
||||
void ping( )
|
||||
{
|
||||
Instant beforeCall = Instant.now( );
|
||||
Response response = given( ).spec( getRequestSpec() )
|
||||
.when( ).get( "/ping" )
|
||||
.then( ).assertThat( ).statusCode( 200 ).and( )
|
||||
.contentType( JSON ).
|
||||
body( "success", equalTo( true ) )
|
||||
.body( "requestTime", notNullValue( ) ).extract().response();
|
||||
OffsetDateTime dateTime = OffsetDateTime.parse( response.body( ).jsonPath( ).getString( "requestTime" ) );
|
||||
Instant afterCall = Instant.now( );
|
||||
assertTrue( dateTime.toInstant( ).isAfter( beforeCall ) );
|
||||
assertTrue( dateTime.toInstant( ).isBefore( afterCall ) );
|
||||
}
|
||||
|
||||
}
|
|
@ -31,7 +31,7 @@
|
|||
<logger name="org.springframework" level="error"/>
|
||||
<logger name="org.apache.archiva.redback.components.cache" level="error"/>
|
||||
<logger name="org.apache.archiva.redback.rest.services.interceptors" level="debug"/>
|
||||
<logger name="org.apache.archiva.redback.rest.services" level="info"/>
|
||||
<logger name="org.apache.archiva.redback.rest.services" level="debug"/>
|
||||
<logger name="org.apache.catalina" level="off" />
|
||||
<logger name="JPOX" level="ERROR"/>
|
||||
<root level="info">
|
||||
|
|
Loading…
Reference in New Issue