Adding V2 REST services

This commit is contained in:
Martin Stockhammer 2021-01-02 11:26:29 +01:00
parent befb43799e
commit d5d9c6d6d1
20 changed files with 2194 additions and 41 deletions

View File

@ -31,6 +31,8 @@
<properties>
<enunciate.docsDir>${project.build.outputDirectory}/rest-docs-archiva-rest-api</enunciate.docsDir>
<site.staging.base>${project.parent.parent.parent.basedir}</site.staging.base>
<openapi.config.file>${project.basedir}/src/main/resources/archiva/openapi-configuration.yaml</openapi.config.file>
<openapi.prefix>archiva</openapi.prefix>
</properties>
<dependencies>
@ -73,6 +75,11 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.archiva.components</groupId>
<artifactId>archiva-components-rest-util</artifactId>
</dependency>
<dependency>
<groupId>jakarta.ws.rs</groupId>
<artifactId>jakarta.ws.rs-api</artifactId>
@ -82,6 +89,23 @@
<artifactId>jakarta.annotation-api</artifactId>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-core</artifactId>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-jaxrs2</artifactId>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
</dependency>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
</dependency>
</dependencies>
<build>

View File

@ -0,0 +1,91 @@
package org.apache.archiva.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 javax.xml.bind.annotation.XmlRootElement;
import java.io.Serializable;
/**
* @author Martin Stockhammer <martin_s@apache.org>
*/
@XmlRootElement(name="beanInformation")
public class BeanInformation implements Serializable
{
private static final long serialVersionUID = -432385743277355987L;
String id;
String displayName;
String descriptionKey;
String defaultDescription;
boolean readonly;
@Schema(description = "The identifier")
public String getId( )
{
return id;
}
public void setId( String id )
{
this.id = id;
}
@Schema(description = "The display name")
public String getDisplayName( )
{
return displayName;
}
public void setDisplayName( String displayName )
{
this.displayName = displayName;
}
@Schema(description = "The translation key for the description")
public String getDescriptionKey( )
{
return descriptionKey;
}
public void setDescriptionKey( String descriptionKey )
{
this.descriptionKey = descriptionKey;
}
@Schema(description = "The description translated in the default language")
public String getDefaultDescription( )
{
return defaultDescription;
}
public void setDefaultDescription( String defaultDescription )
{
this.defaultDescription = defaultDescription;
}
@Schema(description = "True, if this bean cannot be removed")
public boolean isReadonly( )
{
return readonly;
}
public void setReadonly( boolean readonly )
{
this.readonly = readonly;
}
}

View File

@ -0,0 +1,152 @@
package org.apache.archiva.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 javax.xml.bind.annotation.XmlRootElement;
import java.io.Serializable;
/**
* @author Olivier Lamy
* @author Martin Stockhammer
* @since 3.0
*/
@XmlRootElement( name = "cacheConfiguration" )
public class CacheConfiguration
implements Serializable
{
private static final long serialVersionUID = 5479989049980673894L;
/**
* TimeToIdleSeconds.
*/
private int timeToIdleSeconds = -1;
/**
* TimeToLiveSeconds.
*/
private int timeToLiveSeconds = -1;
/**
* max elements in memory.
*/
private int maxEntriesInMemory = -1;
/**
* max elements on disk.
*/
private int maxEntriesOnDisk = -1;
public CacheConfiguration()
{
// no op
}
public CacheConfiguration of( org.apache.archiva.admin.model.beans.CacheConfiguration beanConfiguration ) {
CacheConfiguration newConfig = new CacheConfiguration( );
newConfig.setMaxEntriesInMemory( beanConfiguration.getMaxElementsInMemory() );
newConfig.setMaxEntriesOnDisk( beanConfiguration.getMaxElementsOnDisk() );
newConfig.setTimeToIdleSeconds( beanConfiguration.getTimeToIdleSeconds( ) );
newConfig.setTimeToLiveSeconds( beanConfiguration.getTimeToLiveSeconds( ) );
return newConfig;
}
@Schema(description = "The maximum number of seconds an element can exist in the cache without being accessed. "+
"The element expires at this limit and will no longer be returned from the cache.")
public int getTimeToIdleSeconds()
{
return timeToIdleSeconds;
}
public void setTimeToIdleSeconds( int timeToIdleSeconds )
{
this.timeToIdleSeconds = timeToIdleSeconds;
}
@Schema(description = "The maximum number of seconds an element can exist in the cache regardless of use. "+
"The element expires at this limit and will no longer be returned from the cache.")
public int getTimeToLiveSeconds()
{
return timeToLiveSeconds;
}
public void setTimeToLiveSeconds( int timeToLiveSeconds )
{
this.timeToLiveSeconds = timeToLiveSeconds;
}
@Schema(description = "The maximum cache entries to keep in memory. If the limit is reached, older entries will be evicted, or persisted on disk.")
public int getMaxEntriesInMemory()
{
return maxEntriesInMemory;
}
public void setMaxEntriesInMemory( int maxEntriesInMemory )
{
this.maxEntriesInMemory = maxEntriesInMemory;
}
@Schema(description = "The maximum cache entries to keep on disk. If the limit is reached, older entries will be evicted.")
public int getMaxEntriesOnDisk()
{
return maxEntriesOnDisk;
}
public void setMaxEntriesOnDisk( int maxEntriesOnDisk )
{
this.maxEntriesOnDisk = maxEntriesOnDisk;
}
@Override
public boolean equals( Object o )
{
if ( this == o ) return true;
if ( o == null || getClass( ) != o.getClass( ) ) return false;
CacheConfiguration that = (CacheConfiguration) o;
if ( timeToIdleSeconds != that.timeToIdleSeconds ) return false;
if ( timeToLiveSeconds != that.timeToLiveSeconds ) return false;
if ( maxEntriesInMemory != that.maxEntriesInMemory ) return false;
return maxEntriesOnDisk == that.maxEntriesOnDisk;
}
@Override
public int hashCode( )
{
int result = timeToIdleSeconds;
result = 31 * result + timeToLiveSeconds;
result = 31 * result + maxEntriesInMemory;
result = 31 * result + maxEntriesOnDisk;
return result;
}
@Override
public String toString()
{
final StringBuilder sb = new StringBuilder();
sb.append( "CacheConfiguration" );
sb.append( "{timeToIdleSeconds=" ).append( timeToIdleSeconds );
sb.append( ", timeToLiveSeconds=" ).append( timeToLiveSeconds );
sb.append( ", maxElementsInMemory=" ).append( maxEntriesInMemory );
sb.append( ", maxElementsOnDisk=" ).append( maxEntriesOnDisk );
sb.append( '}' );
return sb.toString();
}
}

View File

@ -0,0 +1,263 @@
package org.apache.archiva.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 javax.xml.bind.annotation.XmlRootElement;
import java.io.Serializable;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
/**
* @author Martin Stockhammer <martin_s@apache.org>
*/
@XmlRootElement(name="ldapConfiguration")
public class LdapConfiguration implements Serializable
{
private static final long serialVersionUID = -4736767846016398583L;
private String hostName = "";
private int port = 389;
private boolean sslEnabled = false;
private String baseDn = "";
private String groupsBaseDn = "";
private String bindDn = "";
private String bindPassword = "";
private String authenticationMethod = "";
private boolean bindAuthenticatorEnabled = true;
private boolean useRoleNameAsGroup = false;
private final Map<String, String> properties = new TreeMap<>();
private boolean writable = false;
public LdapConfiguration( )
{
}
public static LdapConfiguration of( org.apache.archiva.admin.model.beans.LdapConfiguration ldapConfiguration ) {
LdapConfiguration newCfg = new LdapConfiguration( );
newCfg.setAuthenticationMethod( ldapConfiguration.getAuthenticationMethod( ) );
newCfg.setBaseDn( ldapConfiguration.getBaseDn( ) );
newCfg.setGroupsBaseDn( ldapConfiguration.getBaseGroupsDn() );
newCfg.setBindDn( ldapConfiguration.getBindDn() );
newCfg.setBindPassword( ldapConfiguration.getPassword() );
newCfg.setBindAuthenticatorEnabled( ldapConfiguration.isBindAuthenticatorEnabled() );
newCfg.setHostName( ldapConfiguration.getHostName( ) );
newCfg.setPort( ldapConfiguration.getPort( ) );
newCfg.setProperties( ldapConfiguration.getExtraProperties( ) );
newCfg.setSslEnabled( ldapConfiguration.isSsl() );
newCfg.setWritable( ldapConfiguration.isWritable() );
return newCfg;
}
@Schema(description = "The hostname to use to connect to the LDAP server")
public String getHostName( )
{
return hostName;
}
public void setHostName( String hostName )
{
this.hostName = hostName;
}
@Schema(description = "The port to use to connect to the LDAP server")
public int getPort( )
{
return port;
}
public void setPort( int port )
{
this.port = port;
}
@Schema(description = "If SSL should be used for connecting the LDAP server")
public boolean isSslEnabled( )
{
return sslEnabled;
}
public void setSslEnabled( boolean sslEnabled )
{
this.sslEnabled = sslEnabled;
}
@Schema(description = "The BASE DN used for the LDAP server")
public String getBaseDn( )
{
return baseDn;
}
public void setBaseDn( String baseDn )
{
this.baseDn = baseDn;
}
@Schema(description = "The distinguished name of the bind user which is used to bind to the LDAP server")
public String getBindDn( )
{
return bindDn;
}
public void setBindDn( String bindDn )
{
this.bindDn = bindDn;
}
@Schema(description = "The password used to bind to the ldap server")
public String getBindPassword( )
{
return bindPassword;
}
public void setBindPassword( String bindPassword )
{
this.bindPassword = bindPassword;
}
@Schema(description = "The distinguished name of the base to use for searching group.")
public String getGroupsBaseDn( )
{
return groupsBaseDn;
}
public void setGroupsBaseDn( String groupsBaseDn )
{
this.groupsBaseDn = groupsBaseDn;
}
@Schema(description = "The authentication method used to bind to the LDAP server (PLAINTEXT, SASL, ...)")
public String getAuthenticationMethod( )
{
return authenticationMethod;
}
public void setAuthenticationMethod( String authenticationMethod )
{
this.authenticationMethod = authenticationMethod;
}
@Schema(description = "True, if the LDAP bind authentication is used for logging in to Archiva")
public boolean isBindAuthenticatorEnabled( )
{
return bindAuthenticatorEnabled;
}
public void setBindAuthenticatorEnabled( boolean bindAuthenticatorEnabled )
{
this.bindAuthenticatorEnabled = bindAuthenticatorEnabled;
}
@Schema(description = "True, if the archiva role name is also the LDAP group name")
public boolean isUseRoleNameAsGroup( )
{
return useRoleNameAsGroup;
}
public void setUseRoleNameAsGroup( boolean useRoleNameAsGroup )
{
this.useRoleNameAsGroup = useRoleNameAsGroup;
}
@Schema(description = "Map of additional properties")
public Map<String, String> getProperties( )
{
return properties;
}
public void setProperties( Map<String, String> properties )
{
this.properties.clear();
this.properties.putAll( properties );
}
@Schema(description = "True, if attributes in the the LDAP server can be edited by Archiva")
public boolean isWritable( )
{
return writable;
}
public void setWritable( boolean writable )
{
this.writable = writable;
}
@Override
public boolean equals( Object o )
{
if ( this == o ) return true;
if ( o == null || getClass( ) != o.getClass( ) ) return false;
LdapConfiguration that = (LdapConfiguration) o;
if ( port != that.port ) return false;
if ( sslEnabled != that.sslEnabled ) return false;
if ( bindAuthenticatorEnabled != that.bindAuthenticatorEnabled ) return false;
if ( useRoleNameAsGroup != that.useRoleNameAsGroup ) return false;
if ( writable != that.writable ) return false;
if ( !Objects.equals( hostName, that.hostName ) ) return false;
if ( !Objects.equals( baseDn, that.baseDn ) ) return false;
if ( !Objects.equals( bindDn, that.bindDn ) ) return false;
if ( !Objects.equals( groupsBaseDn, that.groupsBaseDn ) )
return false;
if ( !Objects.equals( bindPassword, that.bindPassword ) ) return false;
if ( !Objects.equals( authenticationMethod, that.authenticationMethod ) )
return false;
return properties.equals( that.properties );
}
@Override
public int hashCode( )
{
int result = hostName != null ? hostName.hashCode( ) : 0;
result = 31 * result + port;
result = 31 * result + ( sslEnabled ? 1 : 0 );
result = 31 * result + ( baseDn != null ? baseDn.hashCode( ) : 0 );
result = 31 * result + ( bindDn != null ? bindDn.hashCode( ) : 0 );
result = 31 * result + ( groupsBaseDn != null ? groupsBaseDn.hashCode( ) : 0 );
result = 31 * result + ( bindPassword != null ? bindPassword.hashCode( ) : 0 );
result = 31 * result + ( authenticationMethod != null ? authenticationMethod.hashCode( ) : 0 );
result = 31 * result + ( bindAuthenticatorEnabled ? 1 : 0 );
result = 31 * result + ( useRoleNameAsGroup ? 1 : 0 );
result = 31 * result + properties.hashCode( );
result = 31 * result + ( writable ? 1 : 0 );
return result;
}
@SuppressWarnings( "StringBufferReplaceableByString" )
@Override
public String toString( )
{
final StringBuilder sb = new StringBuilder( "LdapConfiguration{" );
sb.append( "hostName='" ).append( hostName ).append( '\'' );
sb.append( ", port=" ).append( port );
sb.append( ", sslEnabled=" ).append( sslEnabled );
sb.append( ", baseDn='" ).append( baseDn ).append( '\'' );
sb.append( ", groupsBaseDn='" ).append( groupsBaseDn ).append( '\'' );
sb.append( ", bindDn='" ).append( bindDn ).append( '\'' );
sb.append( ", bindPassword='" ).append( bindPassword ).append( '\'' );
sb.append( ", authenticationMethod='" ).append( authenticationMethod ).append( '\'' );
sb.append( ", bindAuthenticatorEnabled=" ).append( bindAuthenticatorEnabled );
sb.append( ", useRoleNameAsGroup=" ).append( useRoleNameAsGroup );
sb.append( ", properties=" ).append( properties );
sb.append( ", writable=" ).append( writable );
sb.append( '}' );
return sb.toString( );
}
}

View File

@ -0,0 +1,163 @@
package org.apache.archiva.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.admin.model.beans.RedbackRuntimeConfiguration;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
/**
* @author Martin Stockhammer <martin_s@apache.org>
*/
@XmlRootElement(name = "securityConfiguration")
public class SecurityConfiguration implements Serializable
{
private static final long serialVersionUID = -4186866365979053029L;
private final List<String> activeUserManagers = new ArrayList<>( );
private final List<String> activeRbacManagers = new ArrayList<>( );
private final Map<String,String> properties = new TreeMap<>( );
private boolean userCacheEnabled=false;
private boolean ldapActive=false;
public SecurityConfiguration() {
}
public static SecurityConfiguration ofRedbackConfiguration( RedbackRuntimeConfiguration configuration ) {
SecurityConfiguration secConfig = new SecurityConfiguration( );
secConfig.setActiveRbacManagers( configuration.getRbacManagerImpls() );
secConfig.setActiveUserManagers( configuration.getUserManagerImpls() );
secConfig.setProperties( configuration.getConfigurationProperties() );
boolean rbLdapActive = configuration.getUserManagerImpls( ).stream( ).anyMatch( um -> um.contains( "ldap" ) );
secConfig.setLdapActive( rbLdapActive );
secConfig.setUserCacheEnabled( configuration.isUseUsersCache() );
return secConfig;
}
@Schema(description = "List of ids of the active user managers")
public List<String> getActiveUserManagers( )
{
return activeUserManagers;
}
public void setActiveUserManagers( List<String> activeUserManagers )
{
this.activeUserManagers.clear();
this.activeUserManagers.addAll( activeUserManagers );
}
public void addSelectedUserManager(String userManager) {
this.activeUserManagers.add( userManager );
}
@Schema(description = "List of ids of the active rbac managers")
public List<String> getActiveRbacManagers( )
{
return activeRbacManagers;
}
public void setActiveRbacManagers( List<String> activeRbacManagers )
{
this.activeRbacManagers.clear();
this.activeRbacManagers.addAll( activeRbacManagers );
}
public void addSelectedRbacManager(String rbacManager) {
this.activeRbacManagers.add( rbacManager );
}
@Schema(description = "Map of all security properties")
public Map<String, String> getProperties( )
{
return properties;
}
public void setProperties( Map<String, String> properties )
{
this.properties.clear();
this.properties.putAll( properties );
}
@Schema(description = "True, if the user cache is active. It caches data from user backend.")
public boolean isUserCacheEnabled( )
{
return userCacheEnabled;
}
public void setUserCacheEnabled( boolean userCacheEnabled )
{
this.userCacheEnabled = userCacheEnabled;
}
@Schema(description = "True, if LDAP is used as user manager")
public boolean isLdapActive( )
{
return ldapActive;
}
public void setLdapActive( boolean ldapActive )
{
this.ldapActive = ldapActive;
}
@Override
public boolean equals( Object o )
{
if ( this == o ) return true;
if ( o == null || getClass( ) != o.getClass( ) ) return false;
SecurityConfiguration that = (SecurityConfiguration) o;
if ( userCacheEnabled != that.userCacheEnabled ) return false;
if ( ldapActive != that.ldapActive ) return false;
if ( !activeUserManagers.equals( that.activeUserManagers ) ) return false;
if ( !activeRbacManagers.equals( that.activeRbacManagers ) ) return false;
return properties.equals( that.properties );
}
@Override
public int hashCode( )
{
int result = activeUserManagers.hashCode( );
result = 31 * result + activeRbacManagers.hashCode( );
result = 31 * result + properties.hashCode( );
result = 31 * result + ( userCacheEnabled ? 1 : 0 );
result = 31 * result + ( ldapActive ? 1 : 0 );
return result;
}
@SuppressWarnings( "StringBufferReplaceableByString" )
@Override
public String toString( )
{
final StringBuilder sb = new StringBuilder( "SecurityConfiguration{" );
sb.append( "selectedUserManagers=" ).append( activeUserManagers );
sb.append( ", selectedRbacManagers=" ).append( activeRbacManagers );
sb.append( ", properties=" ).append( properties );
sb.append( ", userCacheEnabled=" ).append( userCacheEnabled );
sb.append( ", ldapActive=" ).append( ldapActive );
sb.append( '}' );
return sb.toString( );
}
}

View File

@ -0,0 +1,71 @@
package org.apache.archiva.rest.api.services.v2;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import io.swagger.v3.oas.annotations.media.Schema;
import org.apache.commons.lang3.StringUtils;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* @author Martin Stockhammer
* @since 3.0
*/
@XmlRootElement( name = "archivaRestError" )
@Schema(name="ArchivaRestError", description = "Contains a list of error messages that resulted from the current REST call")
public class ArchivaRestError
implements Serializable
{
private static final long serialVersionUID = -8892617571273167067L;
private List<ErrorMessage> errorMessages = new ArrayList<ErrorMessage>( 1 );
public ArchivaRestError()
{
// no op
}
public ArchivaRestError( ArchivaRestServiceException e )
{
errorMessages.addAll( e.getErrorMessages() );
if ( e.getErrorMessages().isEmpty() && StringUtils.isNotEmpty( e.getMessage() ) )
{
errorMessages.add( new ErrorMessage( e.getMessage(), null ) );
}
}
@Schema(name="errorMessages", description = "The list of errors that occurred while processing the REST request")
public List<ErrorMessage> getErrorMessages()
{
return errorMessages;
}
public void setErrorMessages( List<ErrorMessage> errorMessages )
{
this.errorMessages = errorMessages;
}
public void addErrorMessage( ErrorMessage errorMessage )
{
this.errorMessages.add( errorMessage );
}
}

View File

@ -0,0 +1,96 @@
package org.apache.archiva.rest.api.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 java.util.ArrayList;
import java.util.List;
/**
* Generic REST Service Exception that contains error information.
*
* @author Martin Stockhammer <martin_s@apache.org>
* @since 3.0
*/
public class ArchivaRestServiceException extends Exception
{
private int httpErrorCode = 500;
private List<ErrorMessage> errorMessages = new ArrayList<ErrorMessage>(0);
public ArchivaRestServiceException( String s )
{
super( s );
}
public ArchivaRestServiceException( String s, int httpErrorCode )
{
super( s );
this.httpErrorCode = httpErrorCode;
}
public ArchivaRestServiceException( ErrorMessage errorMessage )
{
errorMessages.add( errorMessage );
}
public ArchivaRestServiceException( ErrorMessage errorMessage, int httpResponseCode )
{
this.httpErrorCode = httpResponseCode;
errorMessages.add( errorMessage );
}
public ArchivaRestServiceException( List<ErrorMessage> errorMessage )
{
errorMessages.addAll( errorMessage );
}
public ArchivaRestServiceException( List<ErrorMessage> errorMessage, int httpResponseCode )
{
this.httpErrorCode = httpResponseCode;
errorMessages.addAll( errorMessage );
}
public int getHttpErrorCode()
{
return httpErrorCode;
}
public void setHttpErrorCode( int httpErrorCode )
{
this.httpErrorCode = httpErrorCode;
}
public List<ErrorMessage> getErrorMessages()
{
if ( errorMessages == null )
{
this.errorMessages = new ArrayList<ErrorMessage>();
}
return errorMessages;
}
public void setErrorMessages( List<ErrorMessage> errorMessages )
{
this.errorMessages = errorMessages;
}
public void addErrorMessage( ErrorMessage errorMessage )
{
this.errorMessages.add( errorMessage );
}
}

View File

@ -0,0 +1,25 @@
package org.apache.archiva.rest.api.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.
*/
/**
* @author Martin Stockhammer <martin_s@apache.org>
*/
public interface Configuration
{
String DEFAULT_PAGE_LIMIT = "10";
}

View File

@ -0,0 +1,103 @@
package org.apache.archiva.rest.api.services.v2;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import io.swagger.v3.oas.annotations.media.Schema;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.Serializable;
/**
* @author Olivier Lamy
* @author Martin Stockhammer
* @since 3.0
*/
@XmlRootElement( name = "errorMessage" )
@Schema(name="ErrorMessage",description = "Information about the error, that occurred while processing the REST request.")
public class ErrorMessage
implements Serializable
{
private String errorKey = "";
private String[] args = EMPTY;
private String message = "";
private static final String[] EMPTY = new String[0];
public ErrorMessage()
{
// no op
}
public ErrorMessage( String errorKey )
{
this.errorKey = errorKey;
this.args = EMPTY;
}
public ErrorMessage( String errorKey, String[] args )
{
this.errorKey = errorKey;
this.args = args;
}
public static ErrorMessage of(String errorKey, String... args) {
return new ErrorMessage( errorKey, args );
}
@Schema(description = "The key of the error message. If this is empty, the message message must be set.")
public String getErrorKey()
{
return errorKey;
}
public void setErrorKey( String errorKey )
{
this.errorKey = errorKey;
}
@Schema(description = "Parameters that can be filled to the translated error message")
public String[] getArgs()
{
return args;
}
public void setArgs( String[] args )
{
this.args = args;
}
@Schema(description = "Full error message. Either additional to the key in the default language, or if the message is without key.")
public String getMessage()
{
return message;
}
public void setMessage( String message )
{
this.message = message;
}
public ErrorMessage message( String message )
{
this.message = message;
return this;
}
}

View File

@ -16,6 +16,33 @@ package org.apache.archiva.rest.api.services.v2;/*
* under the License.
*/
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
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;
import org.apache.archiva.components.rest.model.PagedResult;
import org.apache.archiva.components.rest.model.PropertyEntry;
import org.apache.archiva.redback.authorization.RedbackAuthorization;
import org.apache.archiva.rest.api.model.v2.BeanInformation;
import org.apache.archiva.rest.api.model.v2.CacheConfiguration;
import org.apache.archiva.rest.api.model.v2.LdapConfiguration;
import org.apache.archiva.rest.api.model.v2.SecurityConfiguration;
import org.apache.archiva.security.common.ArchivaRoleConstants;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import java.util.List;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import static org.apache.archiva.rest.api.services.v2.Configuration.DEFAULT_PAGE_LIMIT;
/**
*
* Service for configuration of redback and security related settings.
@ -23,6 +50,145 @@ package org.apache.archiva.rest.api.services.v2;/*
* @author Martin Stockhammer <martin_s@apache.org>
* @since 3.0
*/
@Path( "/security" )
@Tag(name = "v2")
@Tag(name = "v2/Security")
@SecurityRequirement(name = "BearerAuth")
public interface SecurityConfigurationService
{
@Path("config")
@GET
@Produces({ MediaType.APPLICATION_JSON })
@RedbackAuthorization(permissions = ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION)
@Operation( summary = "Returns the security configuration that is currently active.",
security = {
@SecurityRequirement(
name = ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION
)
},
responses = {
@ApiResponse( responseCode = "200",
description = "If the configuration could be retrieved"
),
@ApiResponse( responseCode = "403", description = "Authenticated user is not permitted to gather the information",
content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = ArchivaRestServiceException.class )) )
}
)
SecurityConfiguration getConfiguration()
throws ArchivaRestServiceException;
@GET
@Produces( { APPLICATION_JSON } )
@RedbackAuthorization( permissions = ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION )
@Operation( summary = "Returns all configuration properties. The result is paged.",
parameters = {
@Parameter(name = "q", description = "Search term"),
@Parameter(name = "offset", description = "The offset of the first element returned"),
@Parameter(name = "limit", description = "Maximum number of items to return in the response"),
@Parameter(name = "orderBy", description = "List of attribute used for sorting (user_id, fullName, email, created"),
@Parameter(name = "order", description = "The sort order. Either ascending (asc) or descending (desc)")
},
security = {
@SecurityRequirement(
name = ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION
)
},
responses = {
@ApiResponse( responseCode = "200",
description = "If the list could be returned",
content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = PagedResult.class))
),
@ApiResponse( responseCode = "403", description = "Authenticated user is not permitted to gather the information",
content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = ArchivaRestServiceException.class )) )
}
)
PagedResult<PropertyEntry> getConfigurationProperties( @QueryParam("q") @DefaultValue( "" ) String searchTerm,
@QueryParam( "offset" ) @DefaultValue( "0" ) Integer offset,
@QueryParam( "limit" ) @DefaultValue( value = DEFAULT_PAGE_LIMIT ) Integer limit,
@QueryParam( "orderBy") @DefaultValue( "id" ) List<String> orderBy,
@QueryParam("order") @DefaultValue( "asc" ) String order ) throws ArchivaRestServiceException;
@Path("ldap")
@GET
@Produces({ MediaType.APPLICATION_JSON })
@RedbackAuthorization(permissions = ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION)
@Operation( summary = "Returns the LDAP configuration that is currently active.",
security = {
@SecurityRequirement(
name = ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION
)
},
responses = {
@ApiResponse( responseCode = "200",
description = "If the configuration could be retrieved"
),
@ApiResponse( responseCode = "403", description = "Authenticated user is not permitted to gather the information",
content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = ArchivaRestServiceException.class )) )
}
)
LdapConfiguration getLdapConfiguration( ) throws ArchivaRestServiceException;
@Path("user/cache")
@GET
@Produces({ MediaType.APPLICATION_JSON })
@RedbackAuthorization(permissions = ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION)
@Operation( summary = "Returns the cache configuration that is currently active.",
security = {
@SecurityRequirement(
name = ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION
)
},
responses = {
@ApiResponse( responseCode = "200",
description = "If the configuration could be retrieved"
),
@ApiResponse( responseCode = "403", description = "Authenticated user is not permitted to gather the information",
content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = ArchivaRestServiceException.class )) )
}
)
CacheConfiguration getCacheConfiguration( ) throws ArchivaRestServiceException;
@Path("user/managers")
@GET
@Produces({ MediaType.APPLICATION_JSON })
@RedbackAuthorization(permissions = ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION)
@Operation( summary = "Returns the available user manager implementations.",
security = {
@SecurityRequirement(
name = ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION
)
},
responses = {
@ApiResponse( responseCode = "200",
description = "If the list could be retrieved"
),
@ApiResponse( responseCode = "403", description = "Authenticated user is not permitted to gather the information",
content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = ArchivaRestServiceException.class )) )
}
)
List<BeanInformation> getAvailableUserManagers()
throws ArchivaRestServiceException;
@Path("rbac/managers")
@GET
@Produces({ MediaType.APPLICATION_JSON })
@RedbackAuthorization(permissions = ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION)
@Operation( summary = "Returns the available RBAC manager implementations.",
security = {
@SecurityRequirement(
name = ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION
)
},
responses = {
@ApiResponse( responseCode = "200",
description = "If the list could be retrieved"
),
@ApiResponse( responseCode = "403", description = "Authenticated user is not permitted to gather the information",
content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = ArchivaRestServiceException.class )) )
}
)
List<BeanInformation> getAvailableRbacManagers()
throws ArchivaRestServiceException;
}

View File

@ -350,6 +350,12 @@
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-features-logging</artifactId>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-rs-extension-providers</artifactId>
<scope>runtime</scope>
</dependency>
<!-- TEST Scope -->
<dependency>
@ -470,6 +476,12 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<!-- Needed for JDK >= 9 -->
<dependency>

View File

@ -0,0 +1,68 @@
package org.apache.archiva.rest.services.interceptors.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.rest.api.services.v2.ArchivaRestError;
import org.apache.archiva.rest.api.services.v2.ArchivaRestServiceException;
import org.springframework.stereotype.Service;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
/**
* Maps exceptions to REST responses.
*
* @author Martin Stockhammer
* @since 3.0
*/
@Provider
@Service( "v2.archivaRestServiceExceptionMapper" )
public class ArchivaRestServiceExceptionMapper
implements ExceptionMapper<ArchivaRestServiceException>
{
@Override
public Response toResponse( final ArchivaRestServiceException e )
{
ArchivaRestError restError = new ArchivaRestError( e );
Response.ResponseBuilder responseBuilder = Response.status( e.getHttpErrorCode() ).entity( restError );
if ( e.getMessage() != null )
{
responseBuilder = responseBuilder.status( new Response.StatusType()
{
public int getStatusCode()
{
return e.getHttpErrorCode();
}
public Response.Status.Family getFamily()
{
return Response.Status.Family.SERVER_ERROR;
}
public String getReasonPhrase()
{
return e.getMessage();
}
} );
}
return responseBuilder.build();
}
}

View File

@ -0,0 +1,98 @@
package org.apache.archiva.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.admin.model.RepositoryAdminException;
import org.apache.archiva.admin.model.beans.RedbackRuntimeConfiguration;
import org.apache.archiva.admin.model.runtime.RedbackRuntimeConfigurationAdmin;
import org.apache.archiva.components.rest.model.PagedResult;
import org.apache.archiva.components.rest.model.PropertyEntry;
import org.apache.archiva.rest.api.model.v2.BeanInformation;
import org.apache.archiva.rest.api.model.v2.CacheConfiguration;
import org.apache.archiva.rest.api.model.v2.LdapConfiguration;
import org.apache.archiva.rest.api.model.v2.SecurityConfiguration;
import org.apache.archiva.rest.api.services.v2.ArchivaRestServiceException;
import org.apache.archiva.rest.api.services.v2.ErrorMessage;
import org.apache.archiva.rest.api.services.v2.SecurityConfigurationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.inject.Inject;
import java.util.List;
import static org.apache.archiva.rest.services.v2.ErrorKeys.REPOSITORY_ADMIN_ERROR;
/**
* @author Martin Stockhammer <martin_s@apache.org>
*/
@Service("v2.defaultSecurityConfigurationService")
public class DefaultSecurityConfigurationService implements SecurityConfigurationService
{
private static final Logger log = LoggerFactory.getLogger( DefaultSecurityConfigurationService.class );
@Inject
private RedbackRuntimeConfigurationAdmin redbackRuntimeConfigurationAdmin;
@Override
public SecurityConfiguration getConfiguration( ) throws ArchivaRestServiceException
{
try
{
RedbackRuntimeConfiguration redbackRuntimeConfiguration =
redbackRuntimeConfigurationAdmin.getRedbackRuntimeConfiguration();
log.debug( "getRedbackRuntimeConfiguration -> {}", redbackRuntimeConfiguration );
return SecurityConfiguration.ofRedbackConfiguration( redbackRuntimeConfiguration );
}
catch ( RepositoryAdminException e )
{
throw new ArchivaRestServiceException( ErrorMessage.of( REPOSITORY_ADMIN_ERROR ) );
}
}
@Override
public PagedResult<PropertyEntry> getConfigurationProperties( String searchTerm, Integer offset, Integer limit, List<String> orderBy, String order ) throws ArchivaRestServiceException
{
return null;
}
@Override
public LdapConfiguration getLdapConfiguration( ) throws ArchivaRestServiceException
{
return null;
}
@Override
public CacheConfiguration getCacheConfiguration( ) throws ArchivaRestServiceException
{
return null;
}
@Override
public List<BeanInformation> getAvailableUserManagers( ) throws ArchivaRestServiceException
{
return null;
}
@Override
public List<BeanInformation> getAvailableRbacManagers( ) throws ArchivaRestServiceException
{
return null;
}
}

View File

@ -0,0 +1,26 @@
package org.apache.archiva.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.
*/
/**
* @author Martin Stockhammer <martin_s@apache.org>
*/
public interface ErrorKeys
{
public static final String REPOSITORY_ADMIN_ERROR = "a.repositoryadmin.error";
}

View File

@ -39,15 +39,31 @@
<context:component-scan
base-package="org.apache.archiva.rest.services,org.apache.archiva.redback.rest.services"/>
<!-- CXF OpenApiFeature -->
<bean id="archivaOpenApiFeature" class="org.apache.cxf.jaxrs.openapi.OpenApiFeature">
<property name="scanKnownConfigLocations" value="false"/>
<property name="configLocation" value="archiva/openapi-configuration.yaml"/>
<property name="scan" value="false"/>
<property name="useContextBasedConfig" value="true"/>
<!-- <property name="scannerClass" value="io.swagger.v3.jaxrs2.integration.JaxrsApplicationScanner"/> -->
</bean>
<bean id="jsonProvider" class="com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider">
<property name="mapper" ref="redbackJacksonJsonMapper"/>
</bean>
<bean id="v2.jsonProvider" class="com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider">
<property name="mapper" ref="v2.redbackJacksonJsonMapper"/>
</bean>
<bean id="xmlProvider" class="com.fasterxml.jackson.jaxrs.xml.JacksonJaxbXMLProvider">
<property name="mapper" ref="redbackJacksonXMLMapper"/>
</bean>
<bean id="redbackJacksonJsonMapper" class="com.fasterxml.jackson.databind.ObjectMapper" />
<bean id="v2.redbackJacksonJsonMapper" class="com.fasterxml.jackson.databind.ObjectMapper" >
</bean>
<bean id="redbackJacksonXMLMapper" class="com.fasterxml.jackson.dataformat.xml.XmlMapper" />
@ -87,6 +103,29 @@
</jaxrs:server>
<jaxrs:server name="v2.archiva" address="/v2/archiva" >
<jaxrs:providers>
<ref bean="v2.jsonProvider" />
<ref bean="bearerAuthInterceptor#rest"/>
<ref bean="permissionInterceptor#rest"/>
<ref bean="requestValidationInterceptor#rest" />
<ref bean="v2.archivaRestServiceExceptionMapper"/>
<ref bean="threadLocalUserCleaner#rest" />
</jaxrs:providers>
<jaxrs:serviceBeans>
<ref bean="v2.defaultSecurityConfigurationService" />
</jaxrs:serviceBeans>
<jaxrs:features>
<ref bean="archivaOpenApiFeature" />
</jaxrs:features>
</jaxrs:server>
<bean name="browse#versionMetadata" class="org.apache.archiva.components.cache.ehcache.EhcacheCache"
init-method="initialize">
<property name="diskPersistent" value="false"/>

View File

@ -0,0 +1,517 @@
package org.apache.archiva.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 com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import io.restassured.RestAssured;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.config.ObjectMapperConfig;
import io.restassured.config.RestAssuredConfig;
import io.restassured.path.json.mapper.factory.Jackson2ObjectMapperFactory;
import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;
import org.apache.archiva.redback.integration.security.role.RedbackRoleConstants;
import org.apache.archiva.redback.rest.services.BaseSetup;
import org.apache.archiva.redback.role.RoleManager;
import org.apache.archiva.redback.role.RoleManagerException;
import org.apache.archiva.redback.users.User;
import org.apache.archiva.redback.users.UserManager;
import org.apache.archiva.redback.users.UserManagerException;
import org.apache.archiva.redback.users.UserNotFoundException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.apache.cxf.transport.servlet.CXFServlet;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.jupiter.api.Tag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.ContextLoaderListener;
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalTime;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import static io.restassured.RestAssured.*;
import static io.restassured.http.ContentType.JSON;
import static org.junit.jupiter.api.Assertions.assertNotNull;
/**
* Native REST tests do not use the JAX-RS client and can be used with a remote
* REST API service. The tests
*
* @author Martin Stockhammer <martin_s@apache.org>
*/
@Tag( "rest-native" )
@Tag( "rest-v2" )
public abstract class AbstractNativeRestServices
{
private AtomicReference<Path> projectDir = new AtomicReference<>();
private AtomicReference<Path> appServerBase = new AtomicReference<>( );
private AtomicReference<Path> basePath = new AtomicReference<>( );
public static final int STOPPED = 0;
public static final int STOPPING = 1;
public static final int STARTING = 2;
public static final int STARTED = 3;
public static final int ERROR = 4;
private final boolean startServer;
private final String serverPort;
private final String baseUri;
private RequestSpecification requestSpec;
protected Logger log = LoggerFactory.getLogger( getClass( ) );
private static AtomicReference<Server> server = new AtomicReference<>( );
private static AtomicReference<ServerConnector> serverConnector = new AtomicReference<>( );
private static AtomicInteger serverStarted = new AtomicInteger( STOPPED );
private UserManager userManager;
private RoleManager roleManager;
private final boolean remoteService;
private String adminToken;
private String adminRefreshToken;
public AbstractNativeRestServices( )
{
this.startServer = BaseSetup.startServer( );
this.serverPort = BaseSetup.getServerPort( );
this.baseUri = BaseSetup.getBaseUri( );
if ( startServer )
{
this.remoteService = false;
} else {
this.remoteService = true;
}
}
protected abstract String getServicePath( );
protected String getSpringConfigLocation( )
{
return "classpath*:META-INF/spring-context.xml,classpath:META-INF/spring-context-native-test.xml";
}
protected RequestSpecification getRequestSpec( )
{
return this.requestSpec;
}
protected String getContextRoot( )
{
return "/api";
}
protected String getServiceBasePath( )
{
return "/v2/archiva";
}
protected String getRedbackServiceBasePath( )
{
return "/v2/redback";
}
protected String getBasePath( )
{
return new StringBuilder( )
.append( getContextRoot( ) )
.append( getServiceBasePath( ) )
.append( getServicePath( ) ).toString( );
}
/**
* Returns the server that was started, or null if not initialized before.
*
* @return
*/
public Server getServer( )
{
return this.server.get( );
}
public int getServerPort( )
{
ServerConnector connector = serverConnector.get( );
if ( connector != null )
{
return connector.getLocalPort( );
}
else
{
return 0;
}
}
/**
* Returns true, if the server does exist and is running.
*
* @return true, if server does exist and is running.
*/
public boolean isServerRunning( )
{
return serverStarted.get( ) == STARTED && this.server.get( ) != null && this.server.get( ).isRunning( );
}
private UserManager getUserManager( )
{
if ( this.userManager == null )
{
UserManager userManager = ContextLoaderListener.getCurrentWebApplicationContext( )
.getBean( "userManager#default", UserManager.class );
assertNotNull( userManager );
this.userManager = userManager;
}
return this.userManager;
}
private RoleManager getRoleManager( )
{
if ( this.roleManager == null )
{
RoleManager roleManager = ContextLoaderListener.getCurrentWebApplicationContext( )
.getBean( "roleManager", RoleManager.class );
assertNotNull( roleManager );
this.roleManager = roleManager;
}
return this.roleManager;
}
protected String getAdminPwd( )
{
return BaseSetup.getAdminPwd( );
}
protected String getAdminUser( )
{
return RedbackRoleConstants.ADMINISTRATOR_ACCOUNT_NAME;
}
private void setupAdminUser( ) throws UserManagerException, RoleManagerException
{
UserManager um = getUserManager( );
User adminUser = null;
try
{
adminUser = um.findUser( getAdminUser( ) );
}
catch ( UserNotFoundException e )
{
// ignore
}
adminUser = um.createUser( getAdminUser( ), "Administrator", "admin@local.home" );
adminUser.setUsername( getAdminUser( ) );
adminUser.setPassword( getAdminPwd( ) );
adminUser.setFullName( "the admin user" );
adminUser.setEmail( "toto@toto.fr" );
adminUser.setPermanent( true );
adminUser.setValidated( true );
adminUser.setLocked( false );
adminUser.setPasswordChangeRequired( false );
if ( adminUser == null )
{
um.addUser( adminUser );
}
else
{
um.updateUser( adminUser, false );
}
getRoleManager( ).assignRole( "system-administrator", adminUser.getUsername( ) );
}
protected Path getProjectDirectory() {
if ( projectDir.get()==null) {
String propVal = System.getProperty("mvn.project.base.dir");
Path newVal;
if (StringUtils.isEmpty(propVal)) {
newVal = Paths.get("").toAbsolutePath();
} else {
newVal = Paths.get(propVal).toAbsolutePath();
}
projectDir.compareAndSet(null, newVal);
}
return projectDir.get();
}
public Path getBasedir()
{
if (basePath.get()==null) {
String baseDir = System.getProperty( "basedir" );
final Path baseDirPath;
if (StringUtils.isNotEmpty( baseDir )) {
baseDirPath = Paths.get( baseDir );
} else {
baseDirPath = getProjectDirectory( );
}
basePath.compareAndSet( null, baseDirPath );
}
return basePath.get( );
}
Path getAppserverBase() {
if (appServerBase.get()==null) {
String basePath = System.getProperty( "appserver.base" );
final Path appserverPath;
if (StringUtils.isNotEmpty( basePath )) {
appserverPath = Paths.get( basePath ).toAbsolutePath( );
} else {
appserverPath = getBasedir( ).resolve( "target" ).resolve( "appserver-base-" + LocalTime.now( ).toSecondOfDay( ) );
}
appServerBase.compareAndSet( null, appserverPath );
}
return appServerBase.get();
}
private void removeAppsubFolder( Path appServerBase, String folder )
throws Exception
{
Path directory = appServerBase.resolve( folder );
if ( Files.exists(directory) )
{
org.apache.archiva.common.utils.FileUtils.deleteDirectory( directory );
}
}
public void startServer( )
throws Exception
{
if ( serverStarted.compareAndSet( STOPPED, STARTING ) )
{
try
{
log.info( "Starting server" );
Path appServerBase = getAppserverBase( );
removeAppsubFolder(appServerBase, "jcr");
removeAppsubFolder(appServerBase, "conf");
removeAppsubFolder(appServerBase, "data");
Server myServer = new Server( );
this.server.set( myServer );
this.serverConnector.set( new ServerConnector( myServer, new HttpConnectionFactory( ) ) );
myServer.addConnector( serverConnector.get( ) );
ServletHolder servletHolder = new ServletHolder( new CXFServlet( ) );
ServletContextHandler context = new ServletContextHandler( ServletContextHandler.SESSIONS );
context.setResourceBase( SystemUtils.JAVA_IO_TMPDIR );
context.setSessionHandler( new SessionHandler( ) );
context.addServlet( servletHolder, getContextRoot( ) + "/*" );
context.setInitParameter( "contextConfigLocation", getSpringConfigLocation( ) );
context.addEventListener( new ContextLoaderListener( ) );
getServer( ).setHandler( context );
getServer( ).start( );
if ( log.isDebugEnabled( ) )
{
log.debug( "Jetty dump: {}", getServer( ).dump( ) );
}
setupAdminUser( );
log.info( "Started server on port {}", getServerPort( ) );
serverStarted.set( STARTED );
}
finally
{
// In case, if the last statement was not reached
serverStarted.compareAndSet( STARTING, ERROR );
}
}
}
public void stopServer( )
throws Exception
{
if ( this.serverStarted.compareAndSet( STARTED, STOPPING ) )
{
try
{
final Server myServer = getServer( );
if ( myServer != null )
{
log.info( "Stopping server" );
myServer.stop( );
}
serverStarted.set( STOPPED );
}
finally
{
serverStarted.compareAndSet( STOPPING, ERROR );
}
}
else
{
log.error( "Serer is not in STARTED state!" );
}
}
protected void setupNative( ) throws Exception
{
if ( this.startServer )
{
startServer( );
}
if ( StringUtils.isNotEmpty( serverPort ) )
{
RestAssured.port = Integer.parseInt( serverPort );
}
else
{
RestAssured.port = getServerPort( );
}
if ( StringUtils.isNotEmpty( baseUri ) )
{
RestAssured.baseURI = baseUri;
}
else
{
RestAssured.baseURI = "http://localhost";
}
String basePath = getBasePath( );
this.requestSpec = getRequestSpecBuilder( ).build( );
RestAssured.basePath = basePath;
RestAssured.config = RestAssuredConfig.config().objectMapperConfig(new ObjectMapperConfig().jackson2ObjectMapperFactory(
new Jackson2ObjectMapperFactory() {
@Override
public ObjectMapper create( Type cls, String charset) {
ObjectMapper om = new ObjectMapper().findAndRegisterModules();
om.configure( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
om.setPropertyNamingStrategy( PropertyNamingStrategy.SNAKE_CASE );
return om;
}
}
));
}
protected RequestSpecBuilder getRequestSpecBuilder( ) {
return getRequestSpecBuilder( null );
}
protected RequestSpecBuilder getRequestSpecBuilder( String basePath )
{
String myBasePath = basePath == null ? getBasePath( ) : basePath;
return new RequestSpecBuilder( ).setBaseUri( baseURI )
.setPort( port )
.setBasePath( myBasePath )
.addHeader( "Origin", RestAssured.baseURI + ":" + RestAssured.port );
}
protected RequestSpecBuilder getAuthRequestSpecBuilder( )
{
return new RequestSpecBuilder( ).setBaseUri( baseURI )
.setPort( port )
.setBasePath( new StringBuilder( )
.append( getContextRoot( ) )
.append( getRedbackServiceBasePath() ).append("/auth").toString() )
.addHeader( "Origin", RestAssured.baseURI + ":" + RestAssured.port );
}
protected RequestSpecification getRequestSpec( String bearerToken )
{
return getRequestSpecBuilder( ).addHeader( "Authorization", "Bearer " + bearerToken ).build( );
}
protected RequestSpecification getRequestSpec( String bearerToken, String path)
{
return getRequestSpecBuilder( path ).addHeader( "Authorization", "Bearer " + bearerToken ).build( );
}
protected void shutdownNative( ) throws Exception
{
if (startServer)
{
stopServer( );
}
}
protected org.apache.archiva.redback.rest.api.model.User addRemoteUser(String userid, String password, String fullName, String mail) {
return null;
}
protected void initAdminToken() {
Map<String, Object> jsonAsMap = new HashMap<>();
jsonAsMap.put( "grant_type", "authorization_code" );
jsonAsMap.put("user_id", getAdminUser());
jsonAsMap.put("password", getAdminPwd() );
Response result = given( ).spec( getAuthRequestSpecBuilder().build() )
.contentType( JSON )
.body( jsonAsMap )
.when( ).post( "/authenticate").then( ).statusCode( 200 )
.extract( ).response( );
this.adminToken = result.body( ).jsonPath( ).getString( "access_token" );
this.adminRefreshToken = result.body( ).jsonPath( ).getString( "refresh_token" );
}
protected String getUserToken(String userId, String password) {
Map<String, Object> jsonAsMap = new HashMap<>();
jsonAsMap.put( "grant_type", "authorization_code" );
jsonAsMap.put("user_id", userId);
jsonAsMap.put("password", password );
Response result = given( ).spec( getAuthRequestSpecBuilder().build() )
.contentType( JSON )
.body( jsonAsMap )
.when( ).post( "/authenticate").prettyPeek().then( ).statusCode( 200 )
.extract( ).response( );
result.getBody( ).prettyPrint( );
return result.body( ).jsonPath( ).getString( "access_token" );
}
protected String getAdminToken() {
if (this.adminToken == null) {
initAdminToken();
}
return this.adminToken;
}
protected String getAdminRefreshToken() {
if (this.adminRefreshToken == null) {
initAdminToken();
}
return this.adminRefreshToken;
}
public boolean isRemoteService() {
return this.remoteService;
}
}

View File

@ -0,0 +1,82 @@
package org.apache.archiva.rest.services.v2;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import io.restassured.response.Response;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static io.restassured.RestAssured.given;
import static io.restassured.http.ContentType.JSON;
import static org.junit.jupiter.api.Assertions.*;
/**
* @author Martin Stockhammer <martin_s@apache.org>
*/
@TestInstance( TestInstance.Lifecycle.PER_CLASS )
@Tag( "rest-native" )
@TestMethodOrder( MethodOrderer.Random.class )
@DisplayName( "Native REST tests for V2 SecurityConfigurationService" )
public class NativeSecurityConfigurationServiceTest extends AbstractNativeRestServices
{
@Override
protected String getServicePath( )
{
return "/security";
}
@BeforeAll
void setup( ) throws Exception
{
super.setupNative( );
}
@AfterAll
void destroy( ) throws Exception
{
super.shutdownNative( );
}
@Test
void testGetConfiguration() {
String token = getAdminToken( );
Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
.when( )
.get( "config" )
.prettyPeek()
.then( ).statusCode( 200 ).extract( ).response( );
assertNotNull( response );
assertEquals( "jpa", response.getBody( ).jsonPath( ).getString( "active_user_managers[0]" ) );
assertEquals( "jpa", response.getBody( ).jsonPath( ).getString( "active_rbac_managers[0]" ) );
assertEquals( "memory", response.getBody( ).jsonPath( ).getString( "properties.\"authentication.jwt.keystoreType\"" ) );
assertEquals("10",response.getBody( ).jsonPath( ).getString( "properties.\"security.policy.allowed.login.attempt\""));
assertTrue( response.getBody( ).jsonPath( ).getBoolean( "user_cache_enabled" ) );
assertFalse( response.getBody( ).jsonPath( ).getBoolean( "ldap_active" ) );
}
}

View File

@ -0,0 +1,143 @@
<?xml version="1.0"?>
<!--
~ 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.
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"
default-lazy-init="true">
<context:annotation-config/>
<context:component-scan
base-package="org.apache.archiva.redback.configuration,org.apache.archiva.redback.keys,org.apache.archiva.rest.services.utils,org.apache.archiva.repository.content"/>
<bean name="scheduler" class="org.apache.archiva.components.scheduler.DefaultScheduler">
<property name="properties">
<props>
<prop key="org.quartz.scheduler.instanceName">scheduler1</prop>
<prop key="org.quartz.threadPool.class">org.quartz.simpl.SimpleThreadPool</prop>
<prop key="org.quartz.threadPool.threadCount">2</prop>
<prop key="org.quartz.threadPool.threadPriority">4</prop>
<prop key="org.quartz.jobStore.class">org.quartz.simpl.RAMJobStore</prop>
</props>
</property>
</bean>
<!-- wire up more basic configuration so it doesn't overwrite any config files -->
<bean name="archivaConfiguration#default" class="org.apache.archiva.configuration.DefaultArchivaConfiguration">
<property name="registry" ref="registry#default"/>
</bean>
<alias name="archivaConfiguration#default" alias="archivaConfiguration"/>
<bean name="registry#default" class="org.apache.archiva.components.registry.commons.CommonsConfigurationRegistry">
<property name="initialConfiguration">
<value>
<![CDATA[
<configuration>
<system/>
<xml fileName="${appserver.base}/conf/archiva.xml" config-forceCreate="true"
config-optional="true"
config-name="org.apache.archiva.base" config-at="org.apache.archiva"/>
<properties fileName="${basedir}/src/test/resources/security.properties" config-optional="true"
config-at="org.apache.archiva.redback"/>
</configuration>
]]>
</value>
</property>
</bean>
<bean name="taskQueueExecutor#repository-scanning"
class="org.apache.archiva.components.taskqueue.execution.ThreadedTaskQueueExecutor" lazy-init="false">
<property name="name" value="repository-scanning"/>
<property name="executor" ref="taskExecutor#repository-scanning"/>
<property name="queue" ref="taskQueue#repository-scanning"/>
</bean>
<!--
<bean id="repository" class="org.apache.jackrabbit.core.RepositoryImpl" destroy-method="shutdown">
<constructor-arg ref="config"/>
</bean>
<bean id="config" class="org.apache.jackrabbit.core.config.RepositoryConfig" factory-method="create">
<constructor-arg value="${basedir}/src/test/repository.xml"/>
<constructor-arg value="${appserver.base}/jcr"/>
</bean>
-->
<bean name="commons-configuration" class="org.apache.archiva.components.registry.commons.CommonsConfigurationRegistry"
init-method="initialize">
<property name="initialConfiguration">
<value>
<![CDATA[
<configuration>
<system/>
<properties fileName="${basedir}/src/test/resources/security.properties" config-optional="true"
config-at="org.apache.archiva.redback"/>
</configuration>
]]>
</value>
</property>
</bean>
<alias name="redbackRuntimeConfigurationAdmin#default" alias="userConfiguration#default"/>
<alias name="authorizer#rbac" alias="authorizer#default"/>
<alias name="userManager#configurable" alias="userManager#default"/>
<!-- ***
JPA settings
*** -->
<bean name="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceXmlLocation" value="classpath:META-INF/persistence-hsqldb.xml" />
<property name="jpaPropertyMap">
<map>
<entry key="openjpa.ConnectionURL" value="jdbc:hsqldb:mem:redback_database" />
<entry key="openjpa.ConnectionDriverName" value="org.hsqldb.jdbcDriver" />
<entry key="openjpa.ConnectionUserName" value="sa" />
<entry key="openjpa.ConnectionPassword" value="" />
<entry key="openjpa.Log" value="${openjpa.Log:DefaultLevel=INFO,Runtime=ERROR,Tool=ERROR,SQL=ERROR,Schema=ERROR,MetaData=ERROR}" />
<entry key="openjpa.jdbc.SynchronizeMappings" value="buildSchema(ForeignKeys=true)" />
<entry key="openjpa.jdbc.MappingDefaults"
value="ForeignKeyDeleteAction=restrict,JoinForeignKeyDeleteAction=restrict"/>
</map>
</property>
</bean>
<bean name="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager" >
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<tx:annotation-driven />
<!-- ***
End of JPA settings
*** -->
</beans>

View File

@ -39,11 +39,11 @@
<loggers>
<logger name="jaxrs" level="info" />
<logger name="org.apache.cxf" level="info" />
<logger name="org.apache.cxf" level="debug" />
<logger name="org.apache.archiva" level="debug" />
<logger name="org.apache.archiva.redback" level="debug"/>
<logger name="com.fasterxml.jackson" level="info" />
<logger name="org.apache.archiva.components.registry.commons" level="error" />
<logger name="org.apache.archiva.components" level="error" />
<logger name="JPOX" level="error"/>
<logger name="org.apache.archiva.rest.services" level="info"/>

92
pom.xml
View File

@ -865,6 +865,47 @@
<artifactId>cxf-rt-features-logging</artifactId>
<version>${cxf.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-rs-service-description-openapi-v3</artifactId>
<version>${cxf.version}</version>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-core</artifactId>
<scope>compile</scope>
<version>${io.swagger.version}</version>
<exclusions>
<exclusion>
<groupId>javax.ws.rs</groupId>
<artifactId>jsr311-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-jaxrs2</artifactId>
<version>${io.swagger.version}</version>
<exclusions>
<exclusion>
<groupId>javax.ws.rs</groupId>
<artifactId>jsr311-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
<version>${io.swagger.version}</version>
<exclusions>
<exclusion>
<groupId>javax.ws.rs</groupId>
<artifactId>jsr311-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.archiva</groupId>
@ -1135,12 +1176,16 @@
<version>${maven.resolver.version}</version>
</dependency>
<dependency>
<groupId>org.apache.archiva.components</groupId>
<artifactId>archiva-components-rest-util</artifactId>
<version>${archiva.comp.version}</version>
</dependency>
<dependency>
<groupId>org.apache.archiva.components</groupId>
<artifactId>archiva-components-expression-evaluator</artifactId>
<version>${archiva.comp.version}</version>
</dependency>
<dependency>
<groupId>org.apache.archiva.components</groupId>
<artifactId>archiva-components-spring-taskqueue</artifactId>
@ -1494,6 +1539,7 @@
</dependency>
<!-- Transitive dependency - fixing version -->
<dependency>
<groupId>com.google.guava</groupId>
@ -1729,6 +1775,12 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>${rest-assured.version}</version>
<scope>test</scope>
</dependency>
<!-- JUNIT 5 -->
<dependency>
@ -1788,44 +1840,6 @@
<version>${cxf.version}</version>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-core</artifactId>
<scope>compile</scope>
<version>${io.swagger.version}</version>
<exclusions>
<exclusion>
<groupId>javax.ws.rs</groupId>
<artifactId>jsr311-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-jaxrs2</artifactId>
<version>${io.swagger.version}</version>
<exclusions>
<exclusion>
<groupId>javax.ws.rs</groupId>
<artifactId>jsr311-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
<version>${io.swagger.version}</version>
<exclusions>
<exclusion>
<groupId>javax.ws.rs</groupId>
<artifactId>jsr311-api</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</dependencyManagement>