Adding refresh token for authentication
diff --git a/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/SimpleTokenData.java b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/SimpleTokenData.java
index fc0de01..e5c2553 100644
--- a/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/SimpleTokenData.java
+++ b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/SimpleTokenData.java
@@ -61,6 +61,22 @@
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;
diff --git a/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/StringToken.java b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/StringToken.java
index c96c4e2..e4ab166 100644
--- a/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/StringToken.java
+++ b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/StringToken.java
@@ -26,23 +26,34 @@
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 @@
{
return metadata;
}
+
+ @Override
+ public String getId( )
+ {
+ return id;
+ }
+
+ @Override
+ public TokenType getType( )
+ {
+ return type;
+ }
}
diff --git a/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/Token.java b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/Token.java
index 221a57b..56b6666 100644
--- a/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/Token.java
+++ b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/Token.java
@@ -26,9 +26,34 @@
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();
}
diff --git a/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/TokenType.java b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/TokenType.java
new file mode 100644
index 0000000..9441d08
--- /dev/null
+++ b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/TokenType.java
@@ -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;
+ }
+}
diff --git a/redback-authentication/redback-authentication-providers/redback-authentication-jwt/src/main/java/org/apache/archiva/redback/authentication/jwt/JwtAuthenticator.java b/redback-authentication/redback-authentication-providers/redback-authentication-jwt/src/main/java/org/apache/archiva/redback/authentication/jwt/JwtAuthenticator.java
index b67a134..de34f94 100644
--- a/redback-authentication/redback-authentication-providers/redback-authentication-jwt/src/main/java/org/apache/archiva/redback/authentication/jwt/JwtAuthenticator.java
+++ b/redback-authentication/redback-authentication-providers/redback-authentication-jwt/src/main/java/org/apache/archiva/redback/authentication/jwt/JwtAuthenticator.java
@@ -21,12 +21,14 @@
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.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.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 @@
{
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 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 @@
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 @@
// 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 @@
{
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 @@
* @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 @@
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 @@
* @return the lifetime as duration
*/
public Duration getTokenLifetime() {
- return this.lifetime;
+ return this.tokenLifetime;
}
/**
@@ -844,7 +939,7 @@
* @param lifetime the lifetime as duration
*/
public void setTokenLifetime(Duration lifetime) {
- this.lifetime = lifetime;
+ this.tokenLifetime = lifetime;
}
public UserConfiguration getUserConfiguration( )
diff --git a/redback-authentication/redback-authentication-providers/redback-authentication-jwt/src/test/java/org/apache/archiva/redback/authentication/jwt/AbstractJwtTest.java b/redback-authentication/redback-authentication-providers/redback-authentication-jwt/src/test/java/org/apache/archiva/redback/authentication/jwt/AbstractJwtTest.java
index 6acf4a0..3ff7128 100644
--- a/redback-authentication/redback-authentication-providers/redback-authentication-jwt/src/test/java/org/apache/archiva/redback/authentication/jwt/AbstractJwtTest.java
+++ b/redback-authentication/redback-authentication-providers/redback-authentication-jwt/src/test/java/org/apache/archiva/redback/authentication/jwt/AbstractJwtTest.java
@@ -29,6 +29,7 @@
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 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 @@
assertTrue( Instant.now( ).isBefore( token.getMetadata( ).validBefore( ) ) );
}
-
- @Test
- void authenticate( )
- {
- }
-
@Test
void renewSigningKey( )
{
@@ -231,5 +227,32 @@
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( ) ) );
+ }
+
}
diff --git a/redback-configuration/src/main/java/org/apache/archiva/redback/configuration/UserConfigurationKeys.java b/redback-configuration/src/main/java/org/apache/archiva/redback/configuration/UserConfigurationKeys.java
index a54ab4d..f1c6e87 100644
--- a/redback-configuration/src/main/java/org/apache/archiva/redback/configuration/UserConfigurationKeys.java
+++ b/redback-configuration/src/main/java/org/apache/archiva/redback/configuration/UserConfigurationKeys.java
@@ -187,25 +187,25 @@
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 @@
/**
- * 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";
+
}
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/PingResult.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/PingResult.java
index 34bde92..c852735 100644
--- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/PingResult.java
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/PingResult.java
@@ -19,6 +19,7 @@
*/
import javax.xml.bind.annotation.XmlRootElement;
+import java.time.OffsetDateTime;
/**
* @author Martin Stockhammer <martin_s@apache.org>
@@ -27,13 +28,15 @@
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 @@
{
this.success = success;
}
+
+ public OffsetDateTime getRequestTime( )
+ {
+ return requestTime;
+ }
+
+ public void setRequestTime( OffsetDateTime requestTime )
+ {
+ this.requestTime = requestTime;
+ }
}
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/RefreshTokenRequest.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/RefreshTokenRequest.java
new file mode 100644
index 0000000..a0302c8
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/RefreshTokenRequest.java
@@ -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;
+ }
+}
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/RequestTokenRequest.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/RequestTokenRequest.java
new file mode 100644
index 0000000..470344d
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/RequestTokenRequest.java
@@ -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;
+ }
+}
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/TokenResponse.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/TokenResponse.java
new file mode 100644
index 0000000..6c9d427
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/TokenResponse.java
@@ -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;
+ }
+}
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/LoginService.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/LoginService.java
index 6f8b448..6ecf666 100644
--- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/LoginService.java
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/LoginService.java
@@ -56,7 +56,7 @@
@GET
@Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
@RedbackAuthorization( noRestriction = true )
- PingResult ping()
+ Boolean ping()
throws RedbackServiceException;
@@ -65,7 +65,7 @@
@GET
@Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
@RedbackAuthorization( noRestriction = false, noPermission = true )
- PingResult pingWithAutz()
+ Boolean pingWithAutz()
throws RedbackServiceException;
/**
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/UserService.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/UserService.java
index 0363c85..1fbc633 100644
--- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/UserService.java
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/UserService.java
@@ -151,7 +151,7 @@
@GET
@Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
@RedbackAuthorization( noRestriction = true )
- PingResult ping()
+ Boolean ping()
throws RedbackServiceException;
@Path( "removeFromCache/{userName}" )
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/AuthenticationService.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/AuthenticationService.java
index 88c8f4d..cf75395 100644
--- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/AuthenticationService.java
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/AuthenticationService.java
@@ -25,7 +25,10 @@
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 @@
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 @@
* 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 @@
@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 @@
* 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;
}
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultLoginService.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultLoginService.java
index a3f5055..d51d3a3 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultLoginService.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultLoginService.java
@@ -126,16 +126,16 @@
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 )
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultUserService.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultUserService.java
index 320dc8f..08ea8af 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultUserService.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultUserService.java
@@ -490,10 +490,10 @@
}
@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 )
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultAuthenticationService.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultAuthenticationService.java
index c7c2af3..d9ea256 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultAuthenticationService.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultAuthenticationService.java
@@ -23,20 +23,18 @@
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.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;
/**
*
@@ -106,37 +98,6 @@
@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()
{
return new PingResult( true);
@@ -148,15 +109,11 @@
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 @@
}
// 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 @@
}
@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 @@
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();
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/LoginServiceTest.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/LoginServiceTest.java
index e1fc37d..11d73d9 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/LoginServiceTest.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/LoginServiceTest.java
@@ -45,6 +45,13 @@
}
@Test
+ public void ping()
+ throws Exception
+ {
+ assertNotNull( getLoginService( null ).ping( ) );
+ }
+
+ @Test
public void createUserThenLog()
throws Exception
{
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/UserServiceTest.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/UserServiceTest.java
index 555004e..ba5a78e 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/UserServiceTest.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/UserServiceTest.java
@@ -57,7 +57,7 @@
public void ping()
throws Exception
{
- Boolean res = getUserService().ping().isSuccess();
+ Boolean res = getUserService().ping();
assertTrue( res.booleanValue() );
}
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractNativeRestServices.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractNativeRestServices.java
new file mode 100644
index 0000000..2c24730
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractNativeRestServices.java
@@ -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();
+ }
+}
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractRestServicesTestV2.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractRestServicesTestV2.java
index ccc9abe..a1419e1 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractRestServicesTestV2.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractRestServicesTestV2.java
@@ -22,10 +22,10 @@
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.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 @@
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 @@
protected String getRestServicesPath()
{
- return "restServices";
+ return "api";
}
public void startServer()
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AuthenticationServiceTest.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AuthenticationServiceTest.java
index 9069a03..b507fc6 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AuthenticationServiceTest.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AuthenticationServiceTest.java
@@ -20,7 +20,9 @@
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 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 @@
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( ) );
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/NativeAuthenticationServiceTest.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/NativeAuthenticationServiceTest.java
new file mode 100644
index 0000000..2a5182a
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/NativeAuthenticationServiceTest.java
@@ -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 ) );
+ }
+
+}
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/resources/log4j2-test.xml b/redback-integrations/redback-rest/redback-rest-services/src/test/resources/log4j2-test.xml
index 5f8bb09..e058bdb 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/test/resources/log4j2-test.xml
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/resources/log4j2-test.xml
@@ -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">