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
-
-
-
-
-
-
-