Improving REST v2 API
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/GrantType.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/GrantType.java
index c9058d1..200dc75 100644
--- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/GrantType.java
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/GrantType.java
@@ -25,12 +25,19 @@
{
@XmlEnumValue( "refresh_token" )
REFRESH_TOKEN("refresh_token"),
+
@XmlEnumValue( "authorization_code" )
- AUTHORIZATION_CODE("authorization_code");
+ AUTHORIZATION_CODE("authorization_code"),
- private String label;
+ @XmlEnumValue( "none" )
+ NONE("none");
- GrantType(String label) {
+ private final String label;
+
+ GrantType(final String label) {
+ if (label==null) {
+ throw new NullPointerException( "Label must not be null" );
+ }
this.label = label;
}
@@ -40,10 +47,19 @@
public static GrantType byLabel(String label) {
for (GrantType value : values()) {
- if (value.equals( label )) {
+ if (value.getLabel().equals( label )) {
return value;
}
}
throw new IllegalArgumentException( "Label does not exist " + label );
}
+
+ @Override
+ public String toString( )
+ {
+ final StringBuilder sb = new StringBuilder( "GrantType{" );
+ sb.append( "label='" ).append( label ).append( '\'' );
+ sb.append( '}' );
+ return sb.toString( );
+ }
}
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/TokenRefreshRequest.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/TokenRefreshRequest.java
new file mode 100644
index 0000000..a474303
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/TokenRefreshRequest.java
@@ -0,0 +1,85 @@
+package org.apache.archiva.redback.rest.api.model.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.swagger.v3.oas.annotations.media.Schema;
+import org.apache.archiva.redback.rest.api.model.GrantType;
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import java.io.Serializable;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+@XmlRootElement( name = "refreshToken" )
+@Schema( name = "TokenRefreshRequest", description = "Information for requesting token from a refresh token" )
+public class TokenRefreshRequest implements Serializable
+{
+
+ private static final long serialVersionUID = 3900011211040344882L;
+ GrantType grantType;
+ String refreshToken;
+ String scope;
+
+ public TokenRefreshRequest( )
+ {
+ }
+
+ public TokenRefreshRequest( GrantType grantType, String refreshToken, String scope )
+ {
+ this.grantType = grantType;
+ this.refreshToken = refreshToken;
+ this.scope = scope;
+ }
+
+ @XmlElement( name = "grant_type", required = true)
+ @Schema(description = "The grant type for requesting the token. 'refresh_token' for token refresh")
+ public GrantType getGrantType( )
+ {
+ return grantType;
+ }
+
+ public void setGrantType( GrantType grantType )
+ {
+ this.grantType = grantType;
+ }
+
+ @XmlElement( name = "refresh_token" )
+ @Schema(description = "The refresh token that is validated before generating the new access token")
+ public String getRefreshToken( )
+ {
+ return refreshToken;
+ }
+
+ public void setRefreshToken( String refreshToken )
+ {
+ this.refreshToken = refreshToken;
+ }
+
+ @XmlElement( name = "scope")
+ @Schema(description = "The scope for the new access token.")
+ 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/v2/TokenRequest.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/TokenRequest.java
index a39d4fc..73f6ad9 100644
--- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/TokenRequest.java
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/TokenRequest.java
@@ -32,8 +32,8 @@
@Schema(name="Request Token Data", description = "Schema used for requesting a Bearer token.")
public class TokenRequest implements Serializable
{
- private static final long serialVersionUID = -4803869713444270526L;
- GrantType grantType = null;
+ private static final long serialVersionUID = -2420082541650525792L;
+ GrantType grantType = GrantType.NONE;
String clientId;
String clientSecret;
String code;
@@ -68,7 +68,12 @@
}
@XmlElement(name = "grant_type", required = true )
- @Schema(description = "The grant type. Currently only 'authorization_code' is supported.")
+ @Schema(
+ name = "grant_type",
+ description = "The grant type. Currently only 'authorization_code' is supported.",
+ allowableValues = {"authorization_code","access_token"},
+ defaultValue = "authorization_code",
+ example = "authorization_code")
public GrantType getGrantType( )
{
return grantType;
@@ -80,6 +85,9 @@
}
@XmlElement(name="client_id", nillable = true)
+ @Schema(
+ name = "client_id",
+ description = "The client identifier.")
public String getClientId( )
{
return clientId;
@@ -91,6 +99,9 @@
}
@XmlElement(name="client_secret", nillable = true)
+ @Schema(
+ name = "client_secret",
+ description = "The client application secret.")
public String getClientSecret( )
{
return clientSecret;
@@ -113,7 +124,7 @@
}
@XmlElement(name="user_id", required = true )
- @Schema(description = "The user identifier.")
+ @Schema(name="user_id", description = "The user identifier.")
public String getUserId( )
{
return userId;
@@ -131,7 +142,6 @@
return password;
}
- @XmlElement(name="password", required = true )
public void setPassword( String password )
{
this.password = password;
@@ -149,6 +159,9 @@
}
@XmlElement(name="redirect_uri" )
+ @Schema(
+ name = "redirect_uri",
+ description = "The URL to redirect to.")
public String getRedirectUri( )
{
return redirectUri;
@@ -170,4 +183,21 @@
this.state = state;
}
+
+ @Override
+ public String toString( )
+ {
+ final StringBuilder sb = new StringBuilder( "TokenRequest{" );
+ sb.append( "grantType=" ).append( grantType );
+ sb.append( ", clientId='" ).append( clientId ).append( '\'' );
+ sb.append( ", clientSecret='" ).append( clientSecret ).append( '\'' );
+ sb.append( ", code='" ).append( code ).append( '\'' );
+ sb.append( ", scope='" ).append( scope ).append( '\'' );
+ sb.append( ", state='" ).append( state ).append( '\'' );
+ sb.append( ", userId='" ).append( userId ).append( '\'' );
+ sb.append( ", password='*******'" );
+ sb.append( ", redirectUri='" ).append( redirectUri ).append( '\'' );
+ sb.append( '}' );
+ return sb.toString( );
+ }
}
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/TokenResponse.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/TokenResponse.java
new file mode 100644
index 0000000..92c1d0e
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/TokenResponse.java
@@ -0,0 +1,157 @@
+package org.apache.archiva.redback.rest.api.model.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.swagger.v3.oas.annotations.media.Schema;
+import org.apache.archiva.redback.authentication.Token;
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import java.io.Serializable;
+import java.time.Duration;
+import java.time.Instant;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+@XmlRootElement(name="token")
+@Schema(name="TokenData", description = "The token response data")
+public class TokenResponse implements Serializable
+{
+
+ private static final long serialVersionUID = 2063260311211245209L;
+ 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")
+ @Schema(name = "access_token", description = "The access token that may be used as Bearer token in the Authorization header")
+ public String getAccessToken( )
+ {
+ return accessToken;
+ }
+
+ public void setAccessToken( String accessToken )
+ {
+ this.accessToken = accessToken;
+ }
+
+ @XmlElement(name="token_type")
+ @Schema(name="token_type", description = "The type of the token. Currently only Bearer Tokens are supported.")
+ public String getTokenType( )
+ {
+ return tokenType;
+ }
+
+ public void setTokenType( String tokenType )
+ {
+ this.tokenType = tokenType;
+ }
+
+ @XmlElement(name="expires_in")
+ @Schema(name="expires_in", description = "The time in seconds. After this time the token will expire and is not valid for authentication.")
+ public long getExpiresIn( )
+ {
+ return expiresIn;
+ }
+
+ public void setExpiresIn( long expiresIn )
+ {
+ this.expiresIn = expiresIn;
+ }
+
+ @XmlElement(name="refresh_token")
+ @Schema(name="refresh_token", description = "The refresh token, that can be used for getting a new access token.")
+ public String getRefreshToken( )
+ {
+ return refreshToken;
+ }
+
+ public void setRefreshToken( String refreshToken )
+ {
+ this.refreshToken = refreshToken;
+ }
+
+ @Schema(description = "Scope of the token. Currently there are no scopes defined.")
+ public String getScope( )
+ {
+ return scope;
+ }
+
+ public void setScope( String scope )
+ {
+ this.scope = scope;
+ }
+
+ @Schema(description = "The state value will be returned, if a state is provided in the request.")
+ 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/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 6ddbcee..769566b 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,11 +25,13 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import org.apache.archiva.redback.authorization.RedbackAuthorization;
import org.apache.archiva.redback.rest.api.model.v2.PingResult;
+import org.apache.archiva.redback.rest.api.model.v2.TokenRefreshRequest;
import org.apache.archiva.redback.rest.api.model.v2.TokenRequest;
-import org.apache.archiva.redback.rest.api.model.TokenResponse;
+import org.apache.archiva.redback.rest.api.model.v2.TokenResponse;
import org.apache.archiva.redback.rest.api.model.User;
import org.apache.archiva.redback.rest.api.services.RedbackServiceException;
+import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
@@ -44,6 +46,7 @@
@Path( "/auth" )
@Tag(name = "v2")
@Tag(name = "v2/Authentication")
+@Consumes( { MediaType.APPLICATION_JSON })
public interface AuthenticationService
{
@@ -107,7 +110,7 @@
}
)
@SecurityRequirement( name="BearerAuth" )
- TokenResponse token( org.apache.archiva.redback.rest.api.model.TokenRequest tokenRequest )
+ TokenResponse token( TokenRefreshRequest tokenRequest )
throws RedbackServiceException;
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/GroupService.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/GroupService.java
index 851f07f..0f42930 100644
--- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/GroupService.java
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/GroupService.java
@@ -21,6 +21,7 @@
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.headers.Header;
+import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -96,7 +97,7 @@
@ApiResponse( responseCode = "201",
description = "If the group addition was successful",
headers = {
- @Header( name="Location", description = "The URL of the created mapping")
+ @Header( name="Location", description = "The URL of the created mapping", schema = @Schema(type="string"))
}
),
@ApiResponse( responseCode = "405", description = "Invalid input" )
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/UserService.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/UserService.java
index d6d1c27..015660c 100644
--- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/UserService.java
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/UserService.java
@@ -20,6 +20,7 @@
*/
import io.swagger.v3.oas.annotations.headers.Header;
+import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -119,13 +120,13 @@
@ApiResponse( responseCode = "201",
description = "If user creation was successful",
headers = {
- @Header( name="Location", description = "The URL of the created mapping")
+ @Header( name="Location", description = "The URL of the created mapping", schema = @Schema(type="string"))
}
),
@ApiResponse( responseCode = "422", description = "Invalid input" ),
@ApiResponse( responseCode = "303", description = "The user exists already",
headers = {
- @Header( name="Location", description = "The URL of existing user")
+ @Header( name="Location", description = "The URL of existing user", schema = @Schema(type="string"))
}
)
}
@@ -187,13 +188,13 @@
@ApiResponse( responseCode = "201",
description = "If user creation was successful",
headers = {
- @Header( name="Location", description = "The URL of the created mapping")
+ @Header( name="Location", description = "The URL of the created mapping", schema = @Schema(type="string"))
}
),
@ApiResponse( responseCode = "422", description = "Invalid input" ),
@ApiResponse( responseCode = "303", description = "The user exists already",
headers = {
- @Header( name="Location", description = "The URL of the existing admin user")
+ @Header( name="Location", description = "The URL of the existing admin user", schema = @Schema(type="string"))
}
)
}
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java
index 2ec5381..7553cf0 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java
@@ -40,6 +40,8 @@
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.container.ContainerResponseContext;
+import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
@@ -68,7 +70,7 @@
@Priority( Priorities.PRECHECK )
public class RequestValidationInterceptor
extends AbstractInterceptor
- implements ContainerRequestFilter
+ implements ContainerRequestFilter, ContainerResponseFilter
{
@@ -113,6 +115,21 @@
private UserConfiguration config;
+ @Override
+ public void filter( ContainerRequestContext requestContext, ContainerResponseContext responseContext ) throws IOException
+ {
+ responseContext.getHeaders().add(
+ "Access-Control-Allow-Origin", "http://localhost:4200");
+ responseContext.getHeaders().add(
+ "Access-Control-Allow-Credentials", "true");
+ responseContext.getHeaders().add(
+ "Access-Control-Allow-Headers",
+ "origin, content-type, accept, authorization");
+ responseContext.getHeaders().add(
+ "Access-Control-Allow-Methods",
+ "GET, POST, PUT, DELETE, OPTIONS, HEAD");
+ }
+
private class HeaderValidationInfo
{
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 d2d99c5..edc7a4f 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
@@ -35,6 +35,7 @@
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.model.v2.PingResult;
+import org.apache.archiva.redback.rest.api.model.v2.TokenRefreshRequest;
import org.apache.archiva.redback.rest.api.model.v2.TokenRequest;
import org.apache.archiva.redback.rest.api.services.RedbackServiceException;
import org.apache.archiva.redback.rest.api.services.v2.AuthenticationService;
@@ -125,6 +126,7 @@
public TokenResponse logIn( TokenRequest loginRequest )
throws RedbackServiceException
{
+ log.debug( "Login request: grantType={}, code={}", loginRequest.getGrantType( ), loginRequest.getCode( ) );
if (!GrantType.AUTHORIZATION_CODE.equals(loginRequest.getGrantType())) {
throw new RedbackServiceException( ErrorMessage.of( ERR_AUTH_BAD_CODE ), Response.Status.FORBIDDEN.getStatusCode( ) );
}
@@ -203,7 +205,7 @@
}
@Override
- public TokenResponse token( org.apache.archiva.redback.rest.api.model.TokenRequest request ) throws RedbackServiceException
+ public TokenResponse token( TokenRefreshRequest request ) throws RedbackServiceException
{
if (!GrantType.REFRESH_TOKEN.equals(request.getGrantType())) {
log.debug( "Bad grant type {}, expected: refresh_token", request.getGrantType( ).name( ).toLowerCase( ) );