This is an automated email from the ASF dual-hosted git repository. martin_s pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/archiva-redback-core.git
commit d9eed318a1d6e82dacf8ecd74eff7b8c42c782a7 Author: Martin Stockhammer <[email protected]> AuthorDate: Tue Oct 27 22:03:00 2020 +0100 Improving REST v2 API --- .../archiva/redback/rest/api/model/GrantType.java | 24 +++- .../rest/api/model/v2/TokenRefreshRequest.java | 85 +++++++++++ .../redback/rest/api/model/v2/TokenRequest.java | 40 +++++- .../redback/rest/api/model/v2/TokenResponse.java | 157 +++++++++++++++++++++ .../api/services/v2/AuthenticationService.java | 7 +- .../redback/rest/api/services/v2/GroupService.java | 3 +- .../redback/rest/api/services/v2/UserService.java | 9 +- .../interceptors/RequestValidationInterceptor.java | 19 ++- .../services/v2/DefaultAuthenticationService.java | 4 +- 9 files changed, 330 insertions(+), 18 deletions(-) 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 @@ public enum GrantType { @XmlEnumValue( "refresh_token" ) REFRESH_TOKEN("refresh_token"), + @XmlEnumValue( "authorization_code" ) - AUTHORIZATION_CODE("authorization_code"); + AUTHORIZATION_CODE("authorization_code"), + + @XmlEnumValue( "none" ) + NONE("none"); - private String label; + private final String label; - GrantType(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 enum GrantType 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 <[email protected]> + */ +@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 @@ import java.io.Serializable; @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 @@ public class TokenRequest implements Serializable } @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 @@ public class TokenRequest implements Serializable } @XmlElement(name="client_id", nillable = true) + @Schema( + name = "client_id", + description = "The client identifier.") public String getClientId( ) { return clientId; @@ -91,6 +99,9 @@ public class TokenRequest implements Serializable } @XmlElement(name="client_secret", nillable = true) + @Schema( + name = "client_secret", + description = "The client application secret.") public String getClientSecret( ) { return clientSecret; @@ -113,7 +124,7 @@ public class TokenRequest implements Serializable } @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 @@ public class TokenRequest implements Serializable return password; } - @XmlElement(name="password", required = true ) public void setPassword( String password ) { this.password = password; @@ -149,6 +159,9 @@ public class TokenRequest implements Serializable } @XmlElement(name="redirect_uri" ) + @Schema( + name = "redirect_uri", + description = "The URL to redirect to.") public String getRedirectUri( ) { return redirectUri; @@ -170,4 +183,21 @@ public class TokenRequest implements Serializable 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 <[email protected]> + */ +@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.security.SecurityRequirement; 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 @@ import javax.ws.rs.core.MediaType; @Path( "/auth" ) @Tag(name = "v2") @Tag(name = "v2/Authentication") +@Consumes( { MediaType.APPLICATION_JSON }) public interface AuthenticationService { @@ -107,7 +110,7 @@ public interface AuthenticationService } ) @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 @@ package org.apache.archiva.redback.rest.api.services.v2; 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 @@ public interface GroupService @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 @@ package org.apache.archiva.redback.rest.api.services.v2; */ 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 @@ public interface UserService @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 @@ public interface UserService @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.inject.Named; 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 @@ import java.util.List; @Priority( Priorities.PRECHECK ) public class RequestValidationInterceptor extends AbstractInterceptor - implements ContainerRequestFilter + implements ContainerRequestFilter, ContainerResponseFilter { @@ -113,6 +115,21 @@ public class RequestValidationInterceptor 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.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.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 class DefaultAuthenticationService 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 @@ public class DefaultAuthenticationService } @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( ) );
