From 87b089a695eb44c85fb4722f5705c0ba967bb365 Mon Sep 17 00:00:00 2001 From: Martin Stockhammer Date: Tue, 7 Jul 2020 22:34:31 +0200 Subject: [PATCH] Adding new v2 REST services --- pom.xml | 5 + .../redback/rest/api/model/LoginRequest.java | 2 +- .../redback/rest/api/model/PagedResult.java | 17 +- .../rest/api/model/PaginationInfo.java | 4 +- .../archiva/redback/rest/api/model/Token.java | 126 +++++++++ .../redback/rest/api/model/UserLogin.java | 71 +++++ .../rest/api/services/LoginService.java | 10 +- ...ervice.java => AuthenticationService.java} | 28 +- .../rest/api/services/v2/GroupService.java | 7 +- .../redback-rest-services/pom.xml | 4 + .../DefaultLdapGroupMappingService.java | 2 + .../rest/services/DefaultLoginService.java | 6 +- .../interceptors/JacksonJsonConfigurator.java | 7 + .../v2/DefaultAuthenticationService.java | 265 ++++++++++++++++++ .../rest/services/v2/DefaultGroupService.java | 11 +- .../resources/META-INF/spring-context.xml | 11 +- .../services/AbstractRestServicesTest.java | 22 ++ .../v2/AuthenticationServiceTest.java | 112 ++++++++ .../rest/services/v2/GroupServiceTest.java | 4 +- 19 files changed, 678 insertions(+), 36 deletions(-) create mode 100644 redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/Token.java create mode 100644 redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/UserLogin.java rename redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/{LoginService.java => AuthenticationService.java} (74%) create mode 100644 redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultAuthenticationService.java create mode 100644 redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AuthenticationServiceTest.java diff --git a/pom.xml b/pom.xml index a12e8afd..205e9fa7 100644 --- a/pom.xml +++ b/pom.xml @@ -524,6 +524,11 @@ jackson-jaxrs-xml-provider ${jackson.version} + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson.version} + com.fasterxml.jackson.core jackson-databind diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/LoginRequest.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/LoginRequest.java index ae7b0d13..825ec88a 100644 --- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/LoginRequest.java +++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/LoginRequest.java @@ -70,7 +70,7 @@ public class LoginRequest final StringBuilder sb = new StringBuilder(); sb.append( "LoginRequest" ); sb.append( "{username='" ).append( username ).append( '\'' ); - sb.append( ", password='" ).append( password ).append( '\'' ); + sb.append( ", password='" ).append( "*********" ).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/PagedResult.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/PagedResult.java index c0af7291..31681109 100644 --- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/PagedResult.java +++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/PagedResult.java @@ -22,37 +22,34 @@ import javax.xml.bind.annotation.XmlRootElement; import java.util.List; /** + * A Paged result puts the data into an envelope * @author Martin Stockhammer */ @XmlRootElement(name="pagedResult") public class PagedResult { PaginationInfo pagination; - List data; + T data; public PagedResult() { } - public PagedResult( long totalCount, long offset, long limit, List data ) { + public PagedResult( long totalCount, long offset, long limit, T data ) { this.data = data; this.pagination = new PaginationInfo( totalCount, offset, limit ); } - public static final PagedResult ofAllElements(long offset, long limit, List elements) { - return new PagedResult( elements.size( ), offset, limit, elements.subList( (int)offset, (int)offset + (int)limit ) ); + public static final PagedResult of(long totalSize, long offset, long limit, T element) { + return new PagedResult( totalSize, offset, limit, element); } - public static final PagedResult ofSegment(long totalSize, long offset, long limit, List elements) { - return new PagedResult( totalSize, offset, limit, elements); - } - - public List getData( ) + public T getData( ) { return data; } - public void setData( List data ) + public void setData( T data ) { this.data = data; } diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/PaginationInfo.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/PaginationInfo.java index 073ea184..9d5dab5e 100644 --- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/PaginationInfo.java +++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/PaginationInfo.java @@ -22,6 +22,9 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; /** + * + * Informational attributes for pagination. + * * @author Martin Stockhammer */ @XmlRootElement(name="pagination") @@ -42,7 +45,6 @@ public class PaginationInfo this.limit = limit; } - @XmlElement(name="total_count") public long getTotalCount( ) { return totalCount; diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/Token.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/Token.java new file mode 100644 index 00000000..2baaa438 --- /dev/null +++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/Token.java @@ -0,0 +1,126 @@ +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.keys.AuthenticationKey; + +import javax.xml.bind.annotation.XmlRootElement; +import java.time.Instant; +import java.util.Date; + +/** + * Represents a authentication token. + * @author Martin Stockhammer + * @since 3.0 + */ +@XmlRootElement( name = "token" ) +public class Token +{ + String key; + Instant created; + Instant expires; + String principal; + String purpose; + + public Token( ) + { + } + + public static Token of( AuthenticationKey key ) { + Token token = new Token( ); + token.setKey( key.getKey() ); + token.setCreated( key.getDateCreated().toInstant() ); + token.setExpires( key.getDateExpires().toInstant() ); + token.setPrincipal( key.getForPrincipal() ); + token.setPurpose( key.getPurpose() ); + return token; + } + + public static Token of( String key, Date created, Date expires, String principal, String purpose) + { + Token token = new Token( ); + token.setKey( key ); + token.setCreated( created.toInstant( ) ); + token.setExpires( expires.toInstant( ) ); + token.setPrincipal( principal ); + token.setPrincipal( purpose ); + return token; + } + + public static Token of( String key, Instant created, Instant expires, String principal, String purpose ) + { + Token token = new Token( ); + token.setKey( key ); + token.setCreated( created ); + token.setExpires( expires ); + token.setPrincipal( principal ); + token.setPrincipal( purpose ); + return token; + } + + public String getKey( ) + { + return key; + } + + public void setKey( String key ) + { + this.key = key; + } + + public Instant getCreated( ) + { + return created; + } + + public void setCreated( Instant created ) + { + this.created = created; + } + + public Instant getExpires( ) + { + return expires; + } + + public void setExpires( Instant expires ) + { + this.expires = expires; + } + + public String getPrincipal( ) + { + return principal; + } + + public void setPrincipal( String principal ) + { + this.principal = principal; + } + + public String getPurpose( ) + { + return purpose; + } + + public void setPurpose( String purpose ) + { + this.purpose = purpose; + } +} diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/UserLogin.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/UserLogin.java new file mode 100644 index 00000000..ffae37a5 --- /dev/null +++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/UserLogin.java @@ -0,0 +1,71 @@ +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.XmlRootElement; +import java.util.Base64; + +/** + * @author Martin Stockhammer + */ +@XmlRootElement(name="userLogin") +public class UserLogin extends User +{ + + String authToken; + String base64AuthToken; + + public UserLogin( ) + { + } + + public UserLogin( String username, String fullName, String email, boolean validated, boolean locked, String authToken ) + { + super( username, fullName, email, validated, locked ); + this.authToken = authToken; + this.base64AuthToken = Base64.getEncoder( ).encodeToString( authToken.getBytes( ) ); + } + + public UserLogin( org.apache.archiva.redback.users.User user, String authToken ) + { + super( user ); + this.authToken = authToken; + this.base64AuthToken = Base64.getEncoder( ).encodeToString( authToken.getBytes( ) ); + } + + public String getAuthToken( ) + { + return authToken; + } + + public void setAuthToken( String authToken ) + { + this.authToken = authToken; + } + + public String getBase64AuthToken( ) + { + return base64AuthToken; + } + + public void setBase64AuthToken( String base64AuthToken ) + { + this.base64AuthToken = base64AuthToken; + } +} 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 1ff1dcd1..6f8b4482 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 @@ -19,6 +19,7 @@ package org.apache.archiva.redback.rest.api.services; * under the License. */ +import io.swagger.v3.oas.annotations.Operation; import org.apache.archiva.redback.authorization.RedbackAuthorization; import org.apache.archiva.redback.keys.AuthenticationKey; import org.apache.archiva.redback.rest.api.model.ActionStatus; @@ -34,20 +35,23 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; +@Deprecated @Path( "/loginService/" ) public interface LoginService { + @Operation( deprecated = true ) @Path( "addAuthenticationKey" ) @GET @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } ) @RedbackAuthorization( noRestriction = true ) - AuthenticationKeyResult addAuthenticationKey( @QueryParam( "providerKey" ) String providedKey, + String addAuthenticationKey( @QueryParam( "providerKey" ) String providedKey, @QueryParam( "principal" ) String principal, @QueryParam( "purpose" ) String purpose, @QueryParam( "expirationMinutes" ) int expirationMinutes ) throws RedbackServiceException; + @Operation( deprecated = true ) @Path( "ping" ) @GET @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } ) @@ -56,6 +60,7 @@ public interface LoginService throws RedbackServiceException; + @Operation( deprecated = true ) @Path( "pingWithAutz" ) @GET @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } ) @@ -67,6 +72,7 @@ public interface LoginService * check username/password and create a http session. * So no more need of reuse username/password for all ajaxRequest */ + @Operation( deprecated = true ) @Path( "logIn" ) @POST @RedbackAuthorization( noRestriction = true, noPermission = true ) @@ -78,6 +84,7 @@ public interface LoginService * simply check if current user has an http session opened with authz passed and return user data * @since 1.4 */ + @Operation( deprecated = true ) @Path( "isLogged" ) @GET @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML } ) @@ -89,6 +96,7 @@ public interface LoginService * clear user http session * @since 1.4 */ + @Operation( deprecated = true ) @Path( "logout" ) @GET @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } ) diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/LoginService.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/AuthenticationService.java similarity index 74% rename from redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/LoginService.java rename to redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/AuthenticationService.java index ad77713f..c518be49 100644 --- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/LoginService.java +++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/AuthenticationService.java @@ -24,7 +24,9 @@ import org.apache.archiva.redback.rest.api.model.ActionStatus; import org.apache.archiva.redback.rest.api.model.AuthenticationKeyResult; import org.apache.archiva.redback.rest.api.model.LoginRequest; import org.apache.archiva.redback.rest.api.model.PingResult; +import org.apache.archiva.redback.rest.api.model.Token; import org.apache.archiva.redback.rest.api.model.User; +import org.apache.archiva.redback.rest.api.model.UserLogin; import org.apache.archiva.redback.rest.api.services.RedbackServiceException; import javax.ws.rs.GET; @@ -34,23 +36,27 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; +/** + * Version 2 of authentication service + */ @Path( "/auth" ) -public interface LoginService +public interface AuthenticationService { @Path( "requestkey" ) @GET - @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } ) + @Produces( { MediaType.APPLICATION_JSON } ) @RedbackAuthorization( noRestriction = true ) - AuthenticationKeyResult addAuthenticationKey( @QueryParam( "providerKey" ) String providedKey, - @QueryParam( "principal" ) String principal, @QueryParam( "purpose" ) String purpose, - @QueryParam( "expirationMinutes" ) int expirationMinutes ) + 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, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } ) + @Produces( { MediaType.APPLICATION_JSON } ) @RedbackAuthorization( noRestriction = true ) PingResult ping() throws RedbackServiceException; @@ -58,7 +64,7 @@ public interface LoginService @Path( "ping/authenticated" ) @GET - @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } ) + @Produces( { MediaType.APPLICATION_JSON } ) @RedbackAuthorization( noRestriction = false, noPermission = true ) PingResult pingWithAutz() throws RedbackServiceException; @@ -70,8 +76,8 @@ public interface LoginService @Path( "authenticate" ) @POST @RedbackAuthorization( noRestriction = true, noPermission = true ) - @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } ) - User logIn( LoginRequest loginRequest ) + @Produces( { MediaType.APPLICATION_JSON } ) + UserLogin logIn( LoginRequest loginRequest ) throws RedbackServiceException; /** @@ -80,7 +86,7 @@ public interface LoginService */ @Path( "isAuthenticated" ) @GET - @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML } ) + @Produces( { MediaType.APPLICATION_JSON } ) @RedbackAuthorization( noRestriction = true ) User isLogged() throws RedbackServiceException; @@ -91,7 +97,7 @@ public interface LoginService */ @Path( "logout" ) @GET - @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } ) + @Produces( { MediaType.APPLICATION_JSON } ) @RedbackAuthorization( noRestriction = true, noPermission = true ) ActionStatus logout() 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 5274986f..ed754ab0 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 @@ -53,6 +53,7 @@ import java.util.List; public interface GroupService { + @Path( "" ) @GET @Produces( {MediaType.APPLICATION_JSON} ) @@ -62,8 +63,8 @@ public interface GroupService @ApiResponse( description = "List of group objects. The number of returned results depend on the pagination parameters offset and limit." ) } ) - PagedResult getGroups( @QueryParam( "offset" ) @DefaultValue( "0" ) Long offset, - @QueryParam( "limit" ) @DefaultValue( value = Long.MAX_VALUE+"" ) Long limit) + PagedResult> getGroups( @QueryParam( "offset" ) @DefaultValue( "0" ) Integer offset, + @QueryParam( "limit" ) @DefaultValue( value = Integer.MAX_VALUE+"" ) Integer limit) throws RedbackServiceException; @@ -87,7 +88,7 @@ public interface GroupService @RedbackAuthorization( permissions = RedbackRoleConstants.CONFIGURATION_EDIT_OPERATION ) @Operation( summary = "Adds a group mapping", responses = { - @ApiResponse( description = "The status of the add action" ), + @ApiResponse( responseCode = "201", description = "The status of the add action" ), @ApiResponse( responseCode = "405", description = "Invalid input" ) } ) diff --git a/redback-integrations/redback-rest/redback-rest-services/pom.xml b/redback-integrations/redback-rest/redback-rest-services/pom.xml index d6682240..ee4a0b8c 100644 --- a/redback-integrations/redback-rest/redback-rest-services/pom.xml +++ b/redback-integrations/redback-rest/redback-rest-services/pom.xml @@ -145,6 +145,10 @@ com.fasterxml.jackson.core jackson-databind + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + org.apache.cxf cxf-rt-frontend-jaxrs diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultLdapGroupMappingService.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultLdapGroupMappingService.java index ff554b3b..b544b321 100644 --- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultLdapGroupMappingService.java +++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultLdapGroupMappingService.java @@ -46,9 +46,11 @@ import java.util.Map; import java.util.stream.Collectors; /** + * @deprecated Use new API version {@link org.apache.archiva.redback.rest.services.v2.DefaultGroupService} * @author Olivier Lamy * @since 2.1 */ +@Deprecated @Service("ldapGroupMappingService#rest") public class DefaultLdapGroupMappingService implements LdapGroupMappingService 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 170635b5..a3f50555 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 @@ -61,9 +61,11 @@ import java.util.List; import java.util.TimeZone; /** + * @deprecated You should use new REST API version {@link org.apache.archiva.redback.rest.api.services.v2.AuthenticationService} * @author Olivier Lamy * @since 1.3 */ +@Deprecated @Service( "loginService#rest" ) public class DefaultLoginService implements LoginService @@ -90,7 +92,7 @@ public class DefaultLoginService } - public AuthenticationKeyResult addAuthenticationKey( String providedKey, String principal, String purpose, int expirationMinutes ) + public String addAuthenticationKey( String providedKey, String principal, String purpose, int expirationMinutes ) throws RedbackServiceException { KeyManager keyManager = securitySystem.getKeyManager(); @@ -121,7 +123,7 @@ public class DefaultLoginService keyManager.addKey( key ); - return new AuthenticationKeyResult( key.getKey( ) ); + return key.getKey( ); } public PingResult ping() diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/JacksonJsonConfigurator.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/JacksonJsonConfigurator.java index da24a700..04c0c88d 100644 --- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/JacksonJsonConfigurator.java +++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/JacksonJsonConfigurator.java @@ -21,6 +21,8 @@ package org.apache.archiva.redback.rest.services.interceptors; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector; import org.eclipse.jetty.util.annotation.Name; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,6 +30,7 @@ import org.springframework.stereotype.Service; import javax.inject.Inject; import javax.inject.Named; +import java.text.SimpleDateFormat; /** * to setup some ObjectMapper configuration @@ -46,6 +49,10 @@ public class JacksonJsonConfigurator { log.info( "configure jackson ObjectMapper" ); objectMapper.disable( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES ); + objectMapper.setAnnotationIntrospector( new JaxbAnnotationIntrospector( objectMapper.getTypeFactory() ) ); + objectMapper.registerModule( new JavaTimeModule( ) ); + objectMapper.setDateFormat( new SimpleDateFormat( "yyyyMMdd'T'HHmmss.SSSZ" ) ); + xmlMapper.disable( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES ); } 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 new file mode 100644 index 00000000..1c933612 --- /dev/null +++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultAuthenticationService.java @@ -0,0 +1,265 @@ +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 org.apache.archiva.redback.authentication.AuthenticationConstants; +import org.apache.archiva.redback.authentication.AuthenticationException; +import org.apache.archiva.redback.authentication.AuthenticationFailureCause; +import org.apache.archiva.redback.authentication.EncryptionFailedException; +import org.apache.archiva.redback.authentication.PasswordBasedAuthenticationDataSource; +import org.apache.archiva.redback.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.User; +import org.apache.archiva.redback.rest.api.model.UserLogin; +import org.apache.archiva.redback.rest.api.services.RedbackServiceException; +import org.apache.archiva.redback.rest.api.services.v2.AuthenticationService; +import org.apache.archiva.redback.system.SecuritySession; +import org.apache.archiva.redback.system.SecuritySystem; +import org.apache.archiva.redback.users.UserManagerException; +import org.apache.archiva.redback.users.UserNotFoundException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.TemporalAmount; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.TimeZone; + +/** + * + * Authentication service provides REST methods for authentication and verification. + * + * @author Olivier Lamy + * @author Martin Stockhammer + * @since 3.0 + */ +@Service( "v2.authenticationService#rest" ) +public class DefaultAuthenticationService + implements AuthenticationService +{ + + private static final Logger log = LoggerFactory.getLogger( DefaultAuthenticationService.class ); + + private SecuritySystem securitySystem; + + private HttpAuthenticator httpAuthenticator; + + @Context + private HttpServletRequest httpServletRequest; + + // validation token lifetime: 3 hours + long tokenLifetime = 1000*3600*3; + + @Inject + public DefaultAuthenticationService( SecuritySystem securitySystem, + @Named( "httpAuthenticator#basic" ) HttpAuthenticator httpAuthenticator ) + { + this.securitySystem = securitySystem; + this.httpAuthenticator = httpAuthenticator; + } + + + @Override + public Token requestOnetimeToken( String providedKey, String principal, String purpose, int expirationSeconds ) + throws RedbackServiceException + { + 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() + throws RedbackServiceException + { + return new PingResult( true); + } + + @Override + public PingResult pingWithAutz() + throws RedbackServiceException + { + return new PingResult( true ); + } + + @Override + public UserLogin logIn( LoginRequest loginRequest ) + throws RedbackServiceException + { + String userName = loginRequest.getUsername(), password = loginRequest.getPassword(); + PasswordBasedAuthenticationDataSource authDataSource = + new PasswordBasedAuthenticationDataSource( userName, password ); + log.debug("Login for {}",userName); + try + { + SecuritySession securitySession = securitySystem.authenticate( authDataSource ); + log.debug("Security session {}", securitySession); + if ( securitySession.getAuthenticationResult().isAuthenticated() ) + { + org.apache.archiva.redback.users.User user = securitySession.getUser(); + log.debug("user {} authenticated", user.getUsername()); + if ( !user.isValidated() ) + { + log.info( "user {} not validated", user.getUsername() ); + return null; + } + UserLogin restUser = buildRestUser( user ); + restUser.setReadOnly( securitySystem.userManagerReadOnly() ); + // validationToken only set during login + try { + String validationToken = securitySystem.getTokenManager().encryptToken(user.getUsername(), tokenLifetime); + restUser.setValidationToken(validationToken); + log.debug("Validation Token set {}",validationToken); + + } catch (EncryptionFailedException e) { + log.error("Validation token could not be created "+e.getMessage()); + } + + // here create an http session + httpAuthenticator.authenticate( authDataSource, httpServletRequest.getSession( true ) ); + return restUser; + } + if ( securitySession.getAuthenticationResult() != null + && securitySession.getAuthenticationResult().getAuthenticationFailureCauses() != null ) + { + List errorMessages = new ArrayList(); + for ( AuthenticationFailureCause authenticationFailureCause : securitySession.getAuthenticationResult().getAuthenticationFailureCauses() ) + { + if ( authenticationFailureCause.getCause() == AuthenticationConstants.AUTHN_NO_SUCH_USER ) + { + errorMessages.add( new ErrorMessage( "incorrect.username.password" ) ); + } + else + { + errorMessages.add( new ErrorMessage().message( authenticationFailureCause.getMessage() ) ); + } + } + + throw new RedbackServiceException( errorMessages ); + } + return null; + } + catch ( AuthenticationException e ) + { + throw new RedbackServiceException( e.getMessage(), Response.Status.FORBIDDEN.getStatusCode() ); + } + catch ( UserNotFoundException | AccountLockedException e ) + { + throw new RedbackServiceException( e.getMessage() ); + } + catch ( MustChangePasswordException e ) + { + return buildRestUser( e.getUser() ); + } + catch ( UserManagerException e ) + { + log.info( "UserManagerException: {}", e.getMessage() ); + List errorMessages = + Arrays.asList( new ErrorMessage().message( "UserManagerException: " + e.getMessage() ) ); + throw new RedbackServiceException( errorMessages ); + } + + } + + @Override + public User isLogged() + throws RedbackServiceException + { + SecuritySession securitySession = httpAuthenticator.getSecuritySession( httpServletRequest.getSession( true ) ); + Boolean isLogged = securitySession != null; + log.debug( "isLogged {}", isLogged ); + 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(); + restUser.setEmail( user.getEmail() ); + restUser.setUsername( user.getUsername() ); + restUser.setPasswordChangeRequired( user.isPasswordChangeRequired() ); + restUser.setLocked( user.isLocked() ); + restUser.setValidated( user.isValidated() ); + restUser.setFullName( user.getFullName() ); + return restUser; + } +} diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultGroupService.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultGroupService.java index ae1bce09..3821416d 100644 --- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultGroupService.java +++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultGroupService.java @@ -40,6 +40,9 @@ import javax.inject.Inject; import javax.inject.Named; import javax.naming.NamingException; import javax.naming.directory.DirContext; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -60,6 +63,9 @@ public class DefaultGroupService { private final Logger log = LoggerFactory.getLogger( getClass() ); + @Context //injected response proxy supporting multiple threads + private HttpServletResponse response; + @Inject @Named(value = "ldapRoleMapper#default") private LdapRoleMapper ldapRoleMapper; @@ -82,7 +88,7 @@ public class DefaultGroupService } @Override - public PagedResult getGroups( Long offset, Long limit ) throws RedbackServiceException + public PagedResult> getGroups( Integer offset, Integer limit ) throws RedbackServiceException { LdapConnection ldapConnection = null; @@ -93,7 +99,7 @@ public class DefaultGroupService ldapConnection = ldapConnectionFactory.getConnection(); context = ldapConnection.getDirContext(); List groups = ldapRoleMapper.getAllGroupObjects( context ); - return PagedResult.ofSegment( groups.size( ), offset, limit, groups.stream( ).skip( offset ).limit( limit ).map( DefaultGroupService::getGroupFromLdap ).collect( Collectors.toList( ) ) ); + return PagedResult.of( groups.size( ), offset, limit, groups.stream( ).skip( offset ).limit( limit ).map( DefaultGroupService::getGroupFromLdap ).collect( Collectors.toList( ) ) ); } catch ( LdapException | MappingException e ) { @@ -138,6 +144,7 @@ public class DefaultGroupService { ldapRoleMapperConfiguration.addLdapMapping( ldapGroupMapping.getGroup(), new ArrayList<>( ldapGroupMapping.getRoleNames() ) ); + response.setStatus( Response.Status.CREATED.getStatusCode() ); } catch ( MappingException e ) { diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/resources/META-INF/spring-context.xml b/redback-integrations/redback-rest/redback-rest-services/src/main/resources/META-INF/spring-context.xml index 356f704e..d54a4c35 100644 --- a/redback-integrations/redback-rest/redback-rest-services/src/main/resources/META-INF/spring-context.xml +++ b/redback-integrations/redback-rest/redback-rest-services/src/main/resources/META-INF/spring-context.xml @@ -49,6 +49,11 @@ + + + + + @@ -58,7 +63,7 @@ - + @@ -81,10 +86,10 @@ - + - + diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/AbstractRestServicesTest.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/AbstractRestServicesTest.java index 972de2ee..fd3d8920 100644 --- a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/AbstractRestServicesTest.java +++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/AbstractRestServicesTest.java @@ -27,6 +27,7 @@ import org.apache.archiva.redback.rest.api.services.LdapGroupMappingService; import org.apache.archiva.redback.rest.api.services.LoginService; import org.apache.archiva.redback.rest.api.services.RoleManagementService; import org.apache.archiva.redback.rest.api.services.UserService; +import org.apache.archiva.redback.rest.api.services.v2.AuthenticationService; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.SystemUtils; import org.apache.cxf.common.util.Base64Utility; @@ -257,6 +258,27 @@ public abstract class AbstractRestServicesTest return service; } + protected AuthenticationService getLoginServiceV2( String authzHeader ) + { + AuthenticationService service = + JAXRSClientFactory.create( "http://localhost:" + getServerPort() + "/" + getRestServicesPath() + "/v2/redback/", + AuthenticationService.class, Collections.singletonList( new JacksonJaxbJsonProvider() ) ); + + // for debuging purpose + WebClient.getConfig( service ).getHttpConduit().getClient().setReceiveTimeout( getTimeout() ); + + if ( authzHeader != null ) + { + WebClient.client( service ).header( "Authorization", authzHeader ); + } + WebClient.client(service).header("Referer","http://localhost:"+getServerPort()); + + WebClient.client( service ).accept( MediaType.APPLICATION_JSON_TYPE ); + WebClient.client( service ).type( MediaType.APPLICATION_JSON_TYPE ); + + return service; + } + protected LdapGroupMappingService getLdapGroupMappingService( String authzHeader ) { 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 new file mode 100644 index 00000000..cf7f5a2f --- /dev/null +++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AuthenticationServiceTest.java @@ -0,0 +1,112 @@ +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 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.User; +import org.apache.archiva.redback.rest.api.services.RedbackServiceException; +import org.apache.archiva.redback.rest.api.services.UserService; +import org.apache.archiva.redback.rest.services.AbstractRestServicesTest; +import org.apache.archiva.redback.rest.services.FakeCreateAdminService; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Olivier Lamy + */ +@RunWith( SpringJUnit4ClassRunner.class ) +@ContextConfiguration( + locations = { "classpath:/spring-context.xml" } ) +public class AuthenticationServiceTest + extends AbstractRestServicesTest +{ + @Test + public void loginAdmin() + throws Exception + { + assertNotNull( getLoginService( null ).logIn( new LoginRequest( RedbackRoleConstants.ADMINISTRATOR_ACCOUNT_NAME, + FakeCreateAdminService.ADMIN_TEST_PWD ) ) ); + } + + @Test + public void createUserThenLog() + throws Exception + { + try + { + + // START SNIPPET: create-user + User user = new User( "toto", "toto the king", "toto@toto.fr", false, false ); + user.setPassword( "foo123" ); + user.setPermanent( false ); + user.setPasswordChangeRequired( false ); + user.setLocked( false ); + user.setValidated( true ); + UserService userService = getUserService( authorizationHeader ); + userService.createUser( user ); + // END SNIPPET: create-user + user = userService.getUser( "toto" ); + assertNotNull( user ); + assertEquals( "toto the king", user.getFullName() ); + assertEquals( "toto@toto.fr", user.getEmail() ); + getLoginServiceV2( encode( "toto", "foo123" ) ).pingWithAutz(); + } + finally + { + getUserService( authorizationHeader ).deleteUser( "toto" ); + getUserService( authorizationHeader ).removeFromCache( "toto" ); + assertNull( getUserService( authorizationHeader ).getUser( "toto" ) ); + } + } + + @Test + public void simpleLogin() throws RedbackServiceException + { + try + { + + // START SNIPPET: create-user + User user = new User( "toto", "toto the king", "toto@toto.fr", false, false ); + user.setPassword( "foo123" ); + user.setPermanent( false ); + user.setPasswordChangeRequired( false ); + user.setLocked( false ); + user.setValidated( true ); + UserService userService = getUserService( authorizationHeader ); + userService.createUser( user ); + // END SNIPPET: create-user + LoginRequest request = new LoginRequest( "toto", "foo123" ); + User result = getLoginServiceV2( "" ).logIn( request ); + assertNotNull( result ); + assertEquals( "toto", result.getUsername( ) ); + + } + finally + { + getUserService( authorizationHeader ).deleteUser( "toto" ); + getUserService( authorizationHeader ).removeFromCache( "toto" ); + assertNull( getUserService( authorizationHeader ).getUser( "toto" ) ); + } + + } + +} diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/GroupServiceTest.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/GroupServiceTest.java index a54144f0..7fcdc181 100644 --- a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/GroupServiceTest.java +++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/GroupServiceTest.java @@ -73,7 +73,7 @@ public class GroupServiceTest protected GroupService getGroupService( String authzHeader ) { GroupService service = - JAXRSClientFactory.create( "http://localhost:" + getServerPort() + "/" + getRestServicesPath() + "/redback/v2/", + JAXRSClientFactory.create( "http://localhost:" + getServerPort() + "/" + getRestServicesPath() + "/v2/redback/", GroupService.class, Collections.singletonList( new JacksonJaxbJsonProvider() ) ); @@ -193,7 +193,7 @@ public class GroupServiceTest { GroupService service = getGroupService( authorizationHeader ); - List allGroups = service.getGroups( Long.valueOf( 0 ), Long.valueOf( Long.MAX_VALUE ) ).getData().stream( ).map( group -> group.getName( ) ).collect( Collectors.toList( ) ); + List allGroups = service.getGroups( Integer.valueOf( 0 ), Integer.valueOf( Integer.MAX_VALUE ) ).getData().stream( ).map( group -> group.getName( ) ).collect( Collectors.toList( ) ); assertThat( allGroups ).isNotNull().isNotEmpty().hasSize( 3 ).containsAll( groups ); }