diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/pom.xml b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/pom.xml index 8071c9123..67efa7acd 100644 --- a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/pom.xml +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/pom.xml @@ -31,6 +31,8 @@ ${project.build.outputDirectory}/rest-docs-archiva-rest-api ${project.parent.parent.parent.basedir} + ${project.basedir}/src/main/resources/archiva/openapi-configuration.yaml + archiva @@ -73,6 +75,11 @@ + + org.apache.archiva.components + archiva-components-rest-util + + jakarta.ws.rs jakarta.ws.rs-api @@ -82,6 +89,23 @@ jakarta.annotation-api + + + io.swagger.core.v3 + swagger-core + + + io.swagger.core.v3 + swagger-jaxrs2 + + + io.swagger.core.v3 + swagger-annotations + + + jakarta.xml.bind + jakarta.xml.bind-api + diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/BeanInformation.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/BeanInformation.java new file mode 100644 index 000000000..2185a6f28 --- /dev/null +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/BeanInformation.java @@ -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 + */ +@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; + } +} diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/CacheConfiguration.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/CacheConfiguration.java new file mode 100644 index 000000000..5f062f089 --- /dev/null +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/CacheConfiguration.java @@ -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(); + } +} diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/LdapConfiguration.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/LdapConfiguration.java new file mode 100644 index 000000000..20026bafb --- /dev/null +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/LdapConfiguration.java @@ -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 + */ +@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 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 getProperties( ) + { + return properties; + } + + public void setProperties( Map 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( ); + } +} diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/SecurityConfiguration.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/SecurityConfiguration.java new file mode 100644 index 000000000..bd72f7684 --- /dev/null +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/SecurityConfiguration.java @@ -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 + */ +@XmlRootElement(name = "securityConfiguration") +public class SecurityConfiguration implements Serializable +{ + private static final long serialVersionUID = -4186866365979053029L; + + private final List activeUserManagers = new ArrayList<>( ); + private final List activeRbacManagers = new ArrayList<>( ); + private final Map 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 getActiveUserManagers( ) + { + return activeUserManagers; + } + + public void setActiveUserManagers( List 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 getActiveRbacManagers( ) + { + return activeRbacManagers; + } + + public void setActiveRbacManagers( List 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 getProperties( ) + { + return properties; + } + + public void setProperties( Map 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( ); + } +} diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/ArchivaRestError.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/ArchivaRestError.java new file mode 100644 index 000000000..93a2b4fea --- /dev/null +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/ArchivaRestError.java @@ -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 errorMessages = new ArrayList( 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 getErrorMessages() + { + return errorMessages; + } + + public void setErrorMessages( List errorMessages ) + { + this.errorMessages = errorMessages; + } + + public void addErrorMessage( ErrorMessage errorMessage ) + { + this.errorMessages.add( errorMessage ); + } +} diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/ArchivaRestServiceException.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/ArchivaRestServiceException.java new file mode 100644 index 000000000..c2ac99059 --- /dev/null +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/ArchivaRestServiceException.java @@ -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 + * @since 3.0 + */ +public class ArchivaRestServiceException extends Exception +{ + private int httpErrorCode = 500; + + private List errorMessages = new ArrayList(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 ) + { + errorMessages.addAll( errorMessage ); + } + + public ArchivaRestServiceException( List errorMessage, int httpResponseCode ) + { + this.httpErrorCode = httpResponseCode; + errorMessages.addAll( errorMessage ); + } + + public int getHttpErrorCode() + { + return httpErrorCode; + } + + public void setHttpErrorCode( int httpErrorCode ) + { + this.httpErrorCode = httpErrorCode; + } + + public List getErrorMessages() + { + if ( errorMessages == null ) + { + this.errorMessages = new ArrayList(); + } + return errorMessages; + } + + public void setErrorMessages( List errorMessages ) + { + this.errorMessages = errorMessages; + } + + public void addErrorMessage( ErrorMessage errorMessage ) + { + this.errorMessages.add( errorMessage ); + } + +} diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/Configuration.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/Configuration.java new file mode 100644 index 000000000..e0b63577a --- /dev/null +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/Configuration.java @@ -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 + */ +public interface Configuration +{ + String DEFAULT_PAGE_LIMIT = "10"; +} diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/ErrorMessage.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/ErrorMessage.java new file mode 100644 index 000000000..7c673cd46 --- /dev/null +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/ErrorMessage.java @@ -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; + } +} diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/SecurityConfigurationService.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/SecurityConfigurationService.java index b494c3f73..59c57c53e 100644 --- a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/SecurityConfigurationService.java +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/SecurityConfigurationService.java @@ -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 * @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 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 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 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 getAvailableRbacManagers() + throws ArchivaRestServiceException; + } diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/pom.xml b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/pom.xml index dad1cd887..2183737cd 100644 --- a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/pom.xml +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/pom.xml @@ -350,6 +350,12 @@ org.apache.cxf cxf-rt-features-logging + + org.apache.cxf + cxf-rt-rs-extension-providers + runtime + + @@ -470,6 +476,12 @@ test + + io.rest-assured + rest-assured + test + + diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/interceptors/v2/ArchivaRestServiceExceptionMapper.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/interceptors/v2/ArchivaRestServiceExceptionMapper.java new file mode 100644 index 000000000..17237cb85 --- /dev/null +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/interceptors/v2/ArchivaRestServiceExceptionMapper.java @@ -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 +{ + @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(); + } +} diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/v2/DefaultSecurityConfigurationService.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/v2/DefaultSecurityConfigurationService.java new file mode 100644 index 000000000..db580338d --- /dev/null +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/v2/DefaultSecurityConfigurationService.java @@ -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 + */ +@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 getConfigurationProperties( String searchTerm, Integer offset, Integer limit, List 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 getAvailableUserManagers( ) throws ArchivaRestServiceException + { + return null; + } + + @Override + public List getAvailableRbacManagers( ) throws ArchivaRestServiceException + { + return null; + } +} diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/v2/ErrorKeys.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/v2/ErrorKeys.java new file mode 100644 index 000000000..85e9c5c3d --- /dev/null +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/v2/ErrorKeys.java @@ -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 + */ +public interface ErrorKeys +{ + public static final String REPOSITORY_ADMIN_ERROR = "a.repositoryadmin.error"; + +} diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/resources/META-INF/spring-context.xml b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/resources/META-INF/spring-context.xml index 7d1f847b3..910fb7e9a 100644 --- a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/resources/META-INF/spring-context.xml +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/resources/META-INF/spring-context.xml @@ -39,15 +39,31 @@ + + + + + + + + + + + + + + + + @@ -87,6 +103,29 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/java/org/apache/archiva/rest/services/v2/AbstractNativeRestServices.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/java/org/apache/archiva/rest/services/v2/AbstractNativeRestServices.java new file mode 100644 index 000000000..06d6f7429 --- /dev/null +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/java/org/apache/archiva/rest/services/v2/AbstractNativeRestServices.java @@ -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 + */ +@Tag( "rest-native" ) +@Tag( "rest-v2" ) +public abstract class AbstractNativeRestServices +{ + private AtomicReference projectDir = new AtomicReference<>(); + private AtomicReference appServerBase = new AtomicReference<>( ); + private AtomicReference 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 = new AtomicReference<>( ); + private static AtomicReference 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 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 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; + } +} diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/java/org/apache/archiva/rest/services/v2/NativeSecurityConfigurationServiceTest.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/java/org/apache/archiva/rest/services/v2/NativeSecurityConfigurationServiceTest.java new file mode 100644 index 000000000..eff49c12c --- /dev/null +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/java/org/apache/archiva/rest/services/v2/NativeSecurityConfigurationServiceTest.java @@ -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 + */ +@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" ) ); + } + +} diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/resources/META-INF/spring-context-native-test.xml b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/resources/META-INF/spring-context-native-test.xml new file mode 100644 index 000000000..79379ad65 --- /dev/null +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/resources/META-INF/spring-context-native-test.xml @@ -0,0 +1,143 @@ + + + + + + + + + + + + scheduler1 + org.quartz.simpl.SimpleThreadPool + 2 + 4 + org.quartz.simpl.RAMJobStore + + + + + + + + + + + + + + + + + + + + + ]]> + + + + + + + + + + + + + + + + + + + + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/resources/log4j2-test.xml b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/resources/log4j2-test.xml index 8e0fce6e0..c0414be38 100644 --- a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/resources/log4j2-test.xml +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/resources/log4j2-test.xml @@ -39,11 +39,11 @@ - + - + diff --git a/pom.xml b/pom.xml index 307466d02..82b0280c1 100644 --- a/pom.xml +++ b/pom.xml @@ -865,6 +865,47 @@ cxf-rt-features-logging ${cxf.version} + + org.apache.cxf + cxf-rt-rs-service-description-openapi-v3 + ${cxf.version} + + + + io.swagger.core.v3 + swagger-core + compile + ${io.swagger.version} + + + javax.ws.rs + jsr311-api + + + + + io.swagger.core.v3 + swagger-jaxrs2 + ${io.swagger.version} + + + javax.ws.rs + jsr311-api + + + + + io.swagger.core.v3 + swagger-annotations + ${io.swagger.version} + + + javax.ws.rs + jsr311-api + + + + org.apache.archiva @@ -1135,12 +1176,16 @@ ${maven.resolver.version} + + org.apache.archiva.components + archiva-components-rest-util + ${archiva.comp.version} + org.apache.archiva.components archiva-components-expression-evaluator ${archiva.comp.version} - org.apache.archiva.components archiva-components-spring-taskqueue @@ -1494,6 +1539,7 @@ + com.google.guava @@ -1729,6 +1775,12 @@ test + + io.rest-assured + rest-assured + ${rest-assured.version} + test + @@ -1788,44 +1840,6 @@ ${cxf.version} - - io.swagger.core.v3 - swagger-core - compile - ${io.swagger.version} - - - javax.ws.rs - jsr311-api - - - - - io.swagger.core.v3 - swagger-jaxrs2 - ${io.swagger.version} - - - javax.ws.rs - jsr311-api - - - - - io.swagger.core.v3 - swagger-annotations - ${io.swagger.version} - - - javax.ws.rs - jsr311-api - - - - - - -