]> source.dussan.org Git - archiva.git/commitdiff
Adding V2 REST services
authorMartin Stockhammer <martin_s@apache.org>
Sat, 2 Jan 2021 10:26:29 +0000 (11:26 +0100)
committerMartin Stockhammer <martin_s@apache.org>
Sat, 2 Jan 2021 10:26:29 +0000 (11:26 +0100)
20 files changed:
archiva-modules/archiva-web/archiva-rest/archiva-rest-api/pom.xml
archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/BeanInformation.java [new file with mode: 0644]
archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/CacheConfiguration.java [new file with mode: 0644]
archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/LdapConfiguration.java [new file with mode: 0644]
archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/SecurityConfiguration.java [new file with mode: 0644]
archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/ArchivaRestError.java [new file with mode: 0644]
archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/ArchivaRestServiceException.java [new file with mode: 0644]
archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/Configuration.java [new file with mode: 0644]
archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/ErrorMessage.java [new file with mode: 0644]
archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/SecurityConfigurationService.java
archiva-modules/archiva-web/archiva-rest/archiva-rest-services/pom.xml
archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/interceptors/v2/ArchivaRestServiceExceptionMapper.java [new file with mode: 0644]
archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/v2/DefaultSecurityConfigurationService.java [new file with mode: 0644]
archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/v2/ErrorKeys.java [new file with mode: 0644]
archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/resources/META-INF/spring-context.xml
archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/java/org/apache/archiva/rest/services/v2/AbstractNativeRestServices.java [new file with mode: 0644]
archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/java/org/apache/archiva/rest/services/v2/NativeSecurityConfigurationServiceTest.java [new file with mode: 0644]
archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/resources/META-INF/spring-context-native-test.xml [new file with mode: 0644]
archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/resources/log4j2-test.xml
pom.xml

index 8071c9123e0c6e583805ffa854aace565b8b561a..67efa7acda8fe2eb32c1eeddb24cdd9b100ffa3d 100644 (file)
@@ -31,6 +31,8 @@
   <properties>
     <enunciate.docsDir>${project.build.outputDirectory}/rest-docs-archiva-rest-api</enunciate.docsDir>
     <site.staging.base>${project.parent.parent.parent.basedir}</site.staging.base>
+    <openapi.config.file>${project.basedir}/src/main/resources/archiva/openapi-configuration.yaml</openapi.config.file>
+    <openapi.prefix>archiva</openapi.prefix>
   </properties>
 
   <dependencies>
         </exclusion>
       </exclusions>
     </dependency>
+    <dependency>
+      <groupId>org.apache.archiva.components</groupId>
+      <artifactId>archiva-components-rest-util</artifactId>
+    </dependency>
+
     <dependency>
       <groupId>jakarta.ws.rs</groupId>
       <artifactId>jakarta.ws.rs-api</artifactId>
       <artifactId>jakarta.annotation-api</artifactId>
     </dependency>
 
+
+      <dependency>
+        <groupId>io.swagger.core.v3</groupId>
+        <artifactId>swagger-core</artifactId>
+      </dependency>
+      <dependency>
+        <groupId>io.swagger.core.v3</groupId>
+        <artifactId>swagger-jaxrs2</artifactId>
+      </dependency>
+      <dependency>
+        <groupId>io.swagger.core.v3</groupId>
+        <artifactId>swagger-annotations</artifactId>
+      </dependency>
+      <dependency>
+        <groupId>jakarta.xml.bind</groupId>
+        <artifactId>jakarta.xml.bind-api</artifactId>
+      </dependency>
   </dependencies>
 
   <build>
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 (file)
index 0000000..2185a6f
--- /dev/null
@@ -0,0 +1,91 @@
+package org.apache.archiva.rest.api.model.v2;/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import java.io.Serializable;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+@XmlRootElement(name="beanInformation")
+public class BeanInformation implements Serializable
+{
+    private static final long serialVersionUID = -432385743277355987L;
+    String id;
+    String displayName;
+    String descriptionKey;
+    String defaultDescription;
+    boolean readonly;
+
+    @Schema(description = "The identifier")
+    public String getId( )
+    {
+        return id;
+    }
+
+    public void setId( String id )
+    {
+        this.id = id;
+    }
+
+    @Schema(description = "The display name")
+    public String getDisplayName( )
+    {
+        return displayName;
+    }
+
+    public void setDisplayName( String displayName )
+    {
+        this.displayName = displayName;
+    }
+
+    @Schema(description = "The translation key for the description")
+    public String getDescriptionKey( )
+    {
+        return descriptionKey;
+    }
+
+    public void setDescriptionKey( String descriptionKey )
+    {
+        this.descriptionKey = descriptionKey;
+    }
+
+    @Schema(description = "The description translated in the default language")
+    public String getDefaultDescription( )
+    {
+        return defaultDescription;
+    }
+
+    public void setDefaultDescription( String defaultDescription )
+    {
+        this.defaultDescription = defaultDescription;
+    }
+
+    @Schema(description = "True, if this bean cannot be removed")
+    public boolean isReadonly( )
+    {
+        return readonly;
+    }
+
+    public void setReadonly( boolean readonly )
+    {
+        this.readonly = readonly;
+    }
+}
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 (file)
index 0000000..5f062f0
--- /dev/null
@@ -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 (file)
index 0000000..20026ba
--- /dev/null
@@ -0,0 +1,263 @@
+package org.apache.archiva.rest.api.model.v2;/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import java.io.Serializable;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TreeMap;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+@XmlRootElement(name="ldapConfiguration")
+public class LdapConfiguration implements Serializable
+{
+    private static final long serialVersionUID = -4736767846016398583L;
+
+    private String hostName = "";
+    private int port = 389;
+    private boolean sslEnabled = false;
+    private String baseDn = "";
+    private String groupsBaseDn = "";
+    private String bindDn = "";
+    private String bindPassword = "";
+    private String authenticationMethod = "";
+    private boolean bindAuthenticatorEnabled = true;
+    private boolean useRoleNameAsGroup = false;
+    private final Map<String, String> properties = new TreeMap<>();
+    private boolean writable = false;
+
+    public LdapConfiguration( )
+    {
+    }
+
+    public static LdapConfiguration of( org.apache.archiva.admin.model.beans.LdapConfiguration ldapConfiguration ) {
+        LdapConfiguration newCfg = new LdapConfiguration( );
+        newCfg.setAuthenticationMethod( ldapConfiguration.getAuthenticationMethod( ) );
+        newCfg.setBaseDn( ldapConfiguration.getBaseDn( ) );
+        newCfg.setGroupsBaseDn( ldapConfiguration.getBaseGroupsDn() );
+        newCfg.setBindDn( ldapConfiguration.getBindDn() );
+        newCfg.setBindPassword( ldapConfiguration.getPassword() );
+        newCfg.setBindAuthenticatorEnabled( ldapConfiguration.isBindAuthenticatorEnabled() );
+        newCfg.setHostName( ldapConfiguration.getHostName( ) );
+        newCfg.setPort( ldapConfiguration.getPort( ) );
+        newCfg.setProperties( ldapConfiguration.getExtraProperties( ) );
+        newCfg.setSslEnabled( ldapConfiguration.isSsl() );
+        newCfg.setWritable( ldapConfiguration.isWritable() );
+        return newCfg;
+    }
+
+    @Schema(description = "The hostname to use to connect to the LDAP server")
+    public String getHostName( )
+    {
+        return hostName;
+    }
+
+    public void setHostName( String hostName )
+    {
+        this.hostName = hostName;
+    }
+
+    @Schema(description = "The port to use to connect to the LDAP server")
+    public int getPort( )
+    {
+        return port;
+    }
+
+    public void setPort( int port )
+    {
+        this.port = port;
+    }
+
+    @Schema(description = "If SSL should be used for connecting the LDAP server")
+    public boolean isSslEnabled( )
+    {
+        return sslEnabled;
+    }
+
+    public void setSslEnabled( boolean sslEnabled )
+    {
+        this.sslEnabled = sslEnabled;
+    }
+
+    @Schema(description = "The BASE DN used for the LDAP server")
+    public String getBaseDn( )
+    {
+        return baseDn;
+    }
+
+    public void setBaseDn( String baseDn )
+    {
+        this.baseDn = baseDn;
+    }
+
+    @Schema(description = "The distinguished name of the bind user which is used to bind to the LDAP server")
+    public String getBindDn( )
+    {
+        return bindDn;
+    }
+
+    public void setBindDn( String bindDn )
+    {
+        this.bindDn = bindDn;
+    }
+
+    @Schema(description = "The password used to bind to the ldap server")
+    public String getBindPassword( )
+    {
+        return bindPassword;
+    }
+
+    public void setBindPassword( String bindPassword )
+    {
+        this.bindPassword = bindPassword;
+    }
+
+    @Schema(description = "The distinguished name of the base to use for searching group.")
+    public String getGroupsBaseDn( )
+    {
+        return groupsBaseDn;
+    }
+
+    public void setGroupsBaseDn( String groupsBaseDn )
+    {
+        this.groupsBaseDn = groupsBaseDn;
+    }
+
+    @Schema(description = "The authentication method used to bind to the LDAP server (PLAINTEXT, SASL, ...)")
+    public String getAuthenticationMethod( )
+    {
+        return authenticationMethod;
+    }
+
+    public void setAuthenticationMethod( String authenticationMethod )
+    {
+        this.authenticationMethod = authenticationMethod;
+    }
+
+    @Schema(description = "True, if the LDAP bind authentication is used for logging in to Archiva")
+    public boolean isBindAuthenticatorEnabled( )
+    {
+        return bindAuthenticatorEnabled;
+    }
+
+    public void setBindAuthenticatorEnabled( boolean bindAuthenticatorEnabled )
+    {
+        this.bindAuthenticatorEnabled = bindAuthenticatorEnabled;
+    }
+
+    @Schema(description = "True, if the archiva role name is also the LDAP group name")
+    public boolean isUseRoleNameAsGroup( )
+    {
+        return useRoleNameAsGroup;
+    }
+
+    public void setUseRoleNameAsGroup( boolean useRoleNameAsGroup )
+    {
+        this.useRoleNameAsGroup = useRoleNameAsGroup;
+    }
+
+    @Schema(description = "Map of additional properties")
+    public Map<String, String> getProperties( )
+    {
+        return properties;
+    }
+
+    public void setProperties( Map<String, String> properties )
+    {
+        this.properties.clear();
+        this.properties.putAll( properties );
+    }
+
+    @Schema(description = "True, if attributes in the the LDAP server can be edited by Archiva")
+    public boolean isWritable( )
+    {
+        return writable;
+    }
+
+    public void setWritable( boolean writable )
+    {
+        this.writable = writable;
+    }
+
+    @Override
+    public boolean equals( Object o )
+    {
+        if ( this == o ) return true;
+        if ( o == null || getClass( ) != o.getClass( ) ) return false;
+
+        LdapConfiguration that = (LdapConfiguration) o;
+
+        if ( port != that.port ) return false;
+        if ( sslEnabled != that.sslEnabled ) return false;
+        if ( bindAuthenticatorEnabled != that.bindAuthenticatorEnabled ) return false;
+        if ( useRoleNameAsGroup != that.useRoleNameAsGroup ) return false;
+        if ( writable != that.writable ) return false;
+        if ( !Objects.equals( hostName, that.hostName ) ) return false;
+        if ( !Objects.equals( baseDn, that.baseDn ) ) return false;
+        if ( !Objects.equals( bindDn, that.bindDn ) ) return false;
+        if ( !Objects.equals( groupsBaseDn, that.groupsBaseDn ) )
+            return false;
+        if ( !Objects.equals( bindPassword, that.bindPassword ) ) return false;
+        if ( !Objects.equals( authenticationMethod, that.authenticationMethod ) )
+            return false;
+        return properties.equals( that.properties );
+    }
+
+    @Override
+    public int hashCode( )
+    {
+        int result = hostName != null ? hostName.hashCode( ) : 0;
+        result = 31 * result + port;
+        result = 31 * result + ( sslEnabled ? 1 : 0 );
+        result = 31 * result + ( baseDn != null ? baseDn.hashCode( ) : 0 );
+        result = 31 * result + ( bindDn != null ? bindDn.hashCode( ) : 0 );
+        result = 31 * result + ( groupsBaseDn != null ? groupsBaseDn.hashCode( ) : 0 );
+        result = 31 * result + ( bindPassword != null ? bindPassword.hashCode( ) : 0 );
+        result = 31 * result + ( authenticationMethod != null ? authenticationMethod.hashCode( ) : 0 );
+        result = 31 * result + ( bindAuthenticatorEnabled ? 1 : 0 );
+        result = 31 * result + ( useRoleNameAsGroup ? 1 : 0 );
+        result = 31 * result + properties.hashCode( );
+        result = 31 * result + ( writable ? 1 : 0 );
+        return result;
+    }
+
+    @SuppressWarnings( "StringBufferReplaceableByString" )
+    @Override
+    public String toString( )
+    {
+        final StringBuilder sb = new StringBuilder( "LdapConfiguration{" );
+        sb.append( "hostName='" ).append( hostName ).append( '\'' );
+        sb.append( ", port=" ).append( port );
+        sb.append( ", sslEnabled=" ).append( sslEnabled );
+        sb.append( ", baseDn='" ).append( baseDn ).append( '\'' );
+        sb.append( ", groupsBaseDn='" ).append( groupsBaseDn ).append( '\'' );
+        sb.append( ", bindDn='" ).append( bindDn ).append( '\'' );
+        sb.append( ", bindPassword='" ).append( bindPassword ).append( '\'' );
+        sb.append( ", authenticationMethod='" ).append( authenticationMethod ).append( '\'' );
+        sb.append( ", bindAuthenticatorEnabled=" ).append( bindAuthenticatorEnabled );
+        sb.append( ", useRoleNameAsGroup=" ).append( useRoleNameAsGroup );
+        sb.append( ", properties=" ).append( properties );
+        sb.append( ", writable=" ).append( writable );
+        sb.append( '}' );
+        return sb.toString( );
+    }
+}
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 (file)
index 0000000..bd72f76
--- /dev/null
@@ -0,0 +1,163 @@
+package org.apache.archiva.rest.api.model.v2;/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import org.apache.archiva.admin.model.beans.RedbackRuntimeConfiguration;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+@XmlRootElement(name = "securityConfiguration")
+public class SecurityConfiguration implements Serializable
+{
+    private static final long serialVersionUID = -4186866365979053029L;
+
+    private final List<String> activeUserManagers = new ArrayList<>(  );
+    private final List<String> activeRbacManagers = new ArrayList<>(  );
+    private final Map<String,String> properties = new TreeMap<>(  );
+    private boolean userCacheEnabled=false;
+    private boolean ldapActive=false;
+
+    public SecurityConfiguration() {
+
+    }
+
+    public static SecurityConfiguration ofRedbackConfiguration( RedbackRuntimeConfiguration configuration ) {
+        SecurityConfiguration secConfig = new SecurityConfiguration( );
+        secConfig.setActiveRbacManagers( configuration.getRbacManagerImpls() );
+        secConfig.setActiveUserManagers( configuration.getUserManagerImpls() );
+        secConfig.setProperties( configuration.getConfigurationProperties() );
+        boolean rbLdapActive = configuration.getUserManagerImpls( ).stream( ).anyMatch( um -> um.contains( "ldap" ) );
+        secConfig.setLdapActive( rbLdapActive );
+        secConfig.setUserCacheEnabled( configuration.isUseUsersCache() );
+        return secConfig;
+    }
+
+    @Schema(description = "List of ids of the active user managers")
+    public List<String> getActiveUserManagers( )
+    {
+        return activeUserManagers;
+    }
+
+    public void setActiveUserManagers( List<String> activeUserManagers )
+    {
+        this.activeUserManagers.clear();
+        this.activeUserManagers.addAll( activeUserManagers );
+    }
+
+    public void addSelectedUserManager(String userManager) {
+        this.activeUserManagers.add( userManager );
+    }
+
+    @Schema(description = "List of ids of the active rbac managers")
+    public List<String> getActiveRbacManagers( )
+    {
+        return activeRbacManagers;
+    }
+
+    public void setActiveRbacManagers( List<String> activeRbacManagers )
+    {
+        this.activeRbacManagers.clear();
+        this.activeRbacManagers.addAll( activeRbacManagers );
+    }
+
+    public void addSelectedRbacManager(String rbacManager) {
+        this.activeRbacManagers.add( rbacManager );
+    }
+
+    @Schema(description = "Map of all security properties")
+    public Map<String, String> getProperties( )
+    {
+        return properties;
+    }
+
+    public void setProperties( Map<String, String> properties )
+    {
+        this.properties.clear();
+        this.properties.putAll( properties );
+    }
+
+    @Schema(description = "True, if the user cache is active. It caches data from user backend.")
+    public boolean isUserCacheEnabled( )
+    {
+        return userCacheEnabled;
+    }
+
+    public void setUserCacheEnabled( boolean userCacheEnabled )
+    {
+        this.userCacheEnabled = userCacheEnabled;
+    }
+
+    @Schema(description = "True, if LDAP is used as user manager")
+    public boolean isLdapActive( )
+    {
+        return ldapActive;
+    }
+
+    public void setLdapActive( boolean ldapActive )
+    {
+        this.ldapActive = ldapActive;
+    }
+
+    @Override
+    public boolean equals( Object o )
+    {
+        if ( this == o ) return true;
+        if ( o == null || getClass( ) != o.getClass( ) ) return false;
+
+        SecurityConfiguration that = (SecurityConfiguration) o;
+
+        if ( userCacheEnabled != that.userCacheEnabled ) return false;
+        if ( ldapActive != that.ldapActive ) return false;
+        if ( !activeUserManagers.equals( that.activeUserManagers ) ) return false;
+        if ( !activeRbacManagers.equals( that.activeRbacManagers ) ) return false;
+        return properties.equals( that.properties );
+    }
+
+    @Override
+    public int hashCode( )
+    {
+        int result = activeUserManagers.hashCode( );
+        result = 31 * result + activeRbacManagers.hashCode( );
+        result = 31 * result + properties.hashCode( );
+        result = 31 * result + ( userCacheEnabled ? 1 : 0 );
+        result = 31 * result + ( ldapActive ? 1 : 0 );
+        return result;
+    }
+
+    @SuppressWarnings( "StringBufferReplaceableByString" )
+    @Override
+    public String toString( )
+    {
+        final StringBuilder sb = new StringBuilder( "SecurityConfiguration{" );
+        sb.append( "selectedUserManagers=" ).append( activeUserManagers );
+        sb.append( ", selectedRbacManagers=" ).append( activeRbacManagers );
+        sb.append( ", properties=" ).append( properties );
+        sb.append( ", userCacheEnabled=" ).append( userCacheEnabled );
+        sb.append( ", ldapActive=" ).append( ldapActive );
+        sb.append( '}' );
+        return sb.toString( );
+    }
+}
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 (file)
index 0000000..93a2b4f
--- /dev/null
@@ -0,0 +1,71 @@
+package org.apache.archiva.rest.api.services.v2;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Martin Stockhammer
+ * @since 3.0
+ */
+@XmlRootElement( name = "archivaRestError" )
+@Schema(name="ArchivaRestError", description = "Contains a list of error messages that resulted from the current REST call")
+public class ArchivaRestError
+    implements Serializable
+{
+
+    private static final long serialVersionUID = -8892617571273167067L;
+    private List<ErrorMessage> errorMessages = new ArrayList<ErrorMessage>( 1 );
+
+    public ArchivaRestError()
+    {
+        // no op
+    }
+
+    public ArchivaRestError( ArchivaRestServiceException e )
+    {
+        errorMessages.addAll( e.getErrorMessages() );
+        if ( e.getErrorMessages().isEmpty() && StringUtils.isNotEmpty( e.getMessage() ) )
+        {
+            errorMessages.add( new ErrorMessage( e.getMessage(), null ) );
+        }
+    }
+
+    @Schema(name="errorMessages", description = "The list of errors that occurred while processing the REST request")
+    public List<ErrorMessage> getErrorMessages()
+    {
+        return errorMessages;
+    }
+
+    public void setErrorMessages( List<ErrorMessage> errorMessages )
+    {
+        this.errorMessages = errorMessages;
+    }
+
+    public void addErrorMessage( ErrorMessage errorMessage )
+    {
+        this.errorMessages.add( errorMessage );
+    }
+}
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 (file)
index 0000000..c2ac990
--- /dev/null
@@ -0,0 +1,96 @@
+package org.apache.archiva.rest.api.services.v2;/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Generic REST Service Exception that contains error information.
+ *
+ * @author Martin Stockhammer <martin_s@apache.org>
+ * @since 3.0
+ */
+public class ArchivaRestServiceException extends Exception
+{
+    private int httpErrorCode = 500;
+
+    private List<ErrorMessage> errorMessages = new ArrayList<ErrorMessage>(0);
+
+    public ArchivaRestServiceException( String s )
+    {
+        super( s );
+    }
+
+    public ArchivaRestServiceException( String s, int httpErrorCode )
+    {
+        super( s );
+        this.httpErrorCode = httpErrorCode;
+    }
+
+    public ArchivaRestServiceException( ErrorMessage errorMessage )
+    {
+        errorMessages.add( errorMessage );
+    }
+
+    public ArchivaRestServiceException( ErrorMessage errorMessage, int httpResponseCode )
+    {
+        this.httpErrorCode = httpResponseCode;
+        errorMessages.add( errorMessage );
+    }
+
+    public ArchivaRestServiceException( List<ErrorMessage> errorMessage )
+    {
+        errorMessages.addAll( errorMessage );
+    }
+
+    public ArchivaRestServiceException( List<ErrorMessage> errorMessage, int httpResponseCode )
+    {
+        this.httpErrorCode = httpResponseCode;
+        errorMessages.addAll( errorMessage );
+    }
+
+    public int getHttpErrorCode()
+    {
+        return httpErrorCode;
+    }
+
+    public void setHttpErrorCode( int httpErrorCode )
+    {
+        this.httpErrorCode = httpErrorCode;
+    }
+
+    public List<ErrorMessage> getErrorMessages()
+    {
+        if ( errorMessages == null )
+        {
+            this.errorMessages = new ArrayList<ErrorMessage>();
+        }
+        return errorMessages;
+    }
+
+    public void setErrorMessages( List<ErrorMessage> errorMessages )
+    {
+        this.errorMessages = errorMessages;
+    }
+
+    public void addErrorMessage( ErrorMessage errorMessage )
+    {
+        this.errorMessages.add( errorMessage );
+    }
+
+}
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 (file)
index 0000000..e0b6357
--- /dev/null
@@ -0,0 +1,25 @@
+package org.apache.archiva.rest.api.services.v2;/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public interface Configuration
+{
+    String DEFAULT_PAGE_LIMIT = "10";
+}
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 (file)
index 0000000..7c673cd
--- /dev/null
@@ -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;
+    }
+}
index b494c3f73e85b83cc7d48b992c5bd8ddbfef9dd6..59c57c53ec88f7bd6d365a19cd7b36343ee58713 100644 (file)
@@ -16,6 +16,33 @@ package org.apache.archiva.rest.api.services.v2;/*
  * under the License.
  */
 
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.apache.archiva.components.rest.model.PagedResult;
+import org.apache.archiva.components.rest.model.PropertyEntry;
+import org.apache.archiva.redback.authorization.RedbackAuthorization;
+import org.apache.archiva.rest.api.model.v2.BeanInformation;
+import org.apache.archiva.rest.api.model.v2.CacheConfiguration;
+import org.apache.archiva.rest.api.model.v2.LdapConfiguration;
+import org.apache.archiva.rest.api.model.v2.SecurityConfiguration;
+import org.apache.archiva.security.common.ArchivaRoleConstants;
+
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import java.util.List;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+import static org.apache.archiva.rest.api.services.v2.Configuration.DEFAULT_PAGE_LIMIT;
+
 /**
  *
  * Service for configuration of redback and security related settings.
@@ -23,6 +50,145 @@ package org.apache.archiva.rest.api.services.v2;/*
  * @author Martin Stockhammer <martin_s@apache.org>
  * @since 3.0
  */
+@Path( "/security" )
+@Tag(name = "v2")
+@Tag(name = "v2/Security")
+@SecurityRequirement(name = "BearerAuth")
 public interface SecurityConfigurationService
 {
+    @Path("config")
+    @GET
+    @Produces({ MediaType.APPLICATION_JSON })
+    @RedbackAuthorization(permissions = ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION)
+    @Operation( summary = "Returns the security configuration that is currently active.",
+        security = {
+            @SecurityRequirement(
+                name = ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION
+            )
+        },
+        responses = {
+            @ApiResponse( responseCode = "200",
+                description = "If the configuration could be retrieved"
+            ),
+            @ApiResponse( responseCode = "403", description = "Authenticated user is not permitted to gather the information",
+                content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = ArchivaRestServiceException.class )) )
+        }
+    )
+    SecurityConfiguration getConfiguration()
+        throws ArchivaRestServiceException;
+
+    @GET
+    @Produces( { APPLICATION_JSON } )
+    @RedbackAuthorization( permissions = ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION )
+    @Operation( summary = "Returns all configuration properties. The result is paged.",
+        parameters = {
+            @Parameter(name = "q", description = "Search term"),
+            @Parameter(name = "offset", description = "The offset of the first element returned"),
+            @Parameter(name = "limit", description = "Maximum number of items to return in the response"),
+            @Parameter(name = "orderBy", description = "List of attribute used for sorting (user_id, fullName, email, created"),
+            @Parameter(name = "order", description = "The sort order. Either ascending (asc) or descending (desc)")
+        },
+        security = {
+            @SecurityRequirement(
+                name = ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION
+            )
+        },
+        responses = {
+            @ApiResponse( responseCode = "200",
+                description = "If the list could be returned",
+                content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = PagedResult.class))
+            ),
+            @ApiResponse( responseCode = "403", description = "Authenticated user is not permitted to gather the information",
+                content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = ArchivaRestServiceException.class )) )
+        }
+    )
+    PagedResult<PropertyEntry> getConfigurationProperties( @QueryParam("q") @DefaultValue( "" ) String searchTerm,
+                                                           @QueryParam( "offset" ) @DefaultValue( "0" ) Integer offset,
+                                                           @QueryParam( "limit" ) @DefaultValue( value = DEFAULT_PAGE_LIMIT ) Integer limit,
+                                                           @QueryParam( "orderBy") @DefaultValue( "id" ) List<String> orderBy,
+                                                           @QueryParam("order") @DefaultValue( "asc" ) String order ) throws ArchivaRestServiceException;
+
+    @Path("ldap")
+    @GET
+    @Produces({ MediaType.APPLICATION_JSON })
+    @RedbackAuthorization(permissions = ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION)
+    @Operation( summary = "Returns the LDAP configuration that is currently active.",
+        security = {
+            @SecurityRequirement(
+                name = ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION
+            )
+        },
+        responses = {
+            @ApiResponse( responseCode = "200",
+                description = "If the configuration could be retrieved"
+            ),
+            @ApiResponse( responseCode = "403", description = "Authenticated user is not permitted to gather the information",
+                content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = ArchivaRestServiceException.class )) )
+        }
+    )
+    LdapConfiguration getLdapConfiguration( ) throws ArchivaRestServiceException;
+
+
+    @Path("user/cache")
+    @GET
+    @Produces({ MediaType.APPLICATION_JSON })
+    @RedbackAuthorization(permissions = ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION)
+    @Operation( summary = "Returns the cache configuration that is currently active.",
+        security = {
+            @SecurityRequirement(
+                name = ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION
+            )
+        },
+        responses = {
+            @ApiResponse( responseCode = "200",
+                description = "If the configuration could be retrieved"
+            ),
+            @ApiResponse( responseCode = "403", description = "Authenticated user is not permitted to gather the information",
+                content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = ArchivaRestServiceException.class )) )
+        }
+    )
+    CacheConfiguration getCacheConfiguration( ) throws ArchivaRestServiceException;
+
+    @Path("user/managers")
+    @GET
+    @Produces({ MediaType.APPLICATION_JSON })
+    @RedbackAuthorization(permissions = ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION)
+    @Operation( summary = "Returns the available user manager implementations.",
+        security = {
+            @SecurityRequirement(
+                name = ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION
+            )
+        },
+        responses = {
+            @ApiResponse( responseCode = "200",
+                description = "If the list could be retrieved"
+            ),
+            @ApiResponse( responseCode = "403", description = "Authenticated user is not permitted to gather the information",
+                content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = ArchivaRestServiceException.class )) )
+        }
+    )
+    List<BeanInformation> getAvailableUserManagers()
+        throws ArchivaRestServiceException;
+
+    @Path("rbac/managers")
+    @GET
+    @Produces({ MediaType.APPLICATION_JSON })
+    @RedbackAuthorization(permissions = ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION)
+    @Operation( summary = "Returns the available RBAC manager implementations.",
+        security = {
+            @SecurityRequirement(
+                name = ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION
+            )
+        },
+        responses = {
+            @ApiResponse( responseCode = "200",
+                description = "If the list could be retrieved"
+            ),
+            @ApiResponse( responseCode = "403", description = "Authenticated user is not permitted to gather the information",
+                content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = ArchivaRestServiceException.class )) )
+        }
+    )
+    List<BeanInformation> getAvailableRbacManagers()
+        throws ArchivaRestServiceException;
+
 }
index dad1cd8879f1b9d2838f88c414ce26630ae74da4..2183737cd0578a41521ece40d9011bf58dd05f1a 100644 (file)
       <groupId>org.apache.cxf</groupId>
       <artifactId>cxf-rt-features-logging</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.apache.cxf</groupId>
+      <artifactId>cxf-rt-rs-extension-providers</artifactId>
+      <scope>runtime</scope>
+    </dependency>
+
 
     <!-- TEST Scope -->
     <dependency>
       <scope>test</scope>
     </dependency>
 
+    <dependency>
+      <groupId>io.rest-assured</groupId>
+      <artifactId>rest-assured</artifactId>
+      <scope>test</scope>
+    </dependency>
+
 
     <!-- Needed for JDK >= 9 -->
     <dependency>
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 (file)
index 0000000..17237cb
--- /dev/null
@@ -0,0 +1,68 @@
+package org.apache.archiva.rest.services.interceptors.v2;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.archiva.rest.api.services.v2.ArchivaRestError;
+import org.apache.archiva.rest.api.services.v2.ArchivaRestServiceException;
+import org.springframework.stereotype.Service;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * Maps exceptions to REST responses.
+ *
+ * @author Martin Stockhammer
+ * @since 3.0
+ */
+@Provider
+@Service( "v2.archivaRestServiceExceptionMapper" )
+public class ArchivaRestServiceExceptionMapper
+    implements ExceptionMapper<ArchivaRestServiceException>
+{
+    @Override
+    public Response toResponse( final ArchivaRestServiceException e )
+    {
+        ArchivaRestError restError = new ArchivaRestError( e );
+
+        Response.ResponseBuilder responseBuilder = Response.status( e.getHttpErrorCode() ).entity( restError );
+        if ( e.getMessage() != null )
+        {
+            responseBuilder = responseBuilder.status( new Response.StatusType()
+            {
+                public int getStatusCode()
+                {
+                    return e.getHttpErrorCode();
+                }
+
+                public Response.Status.Family getFamily()
+                {
+                    return Response.Status.Family.SERVER_ERROR;
+                }
+
+                public String getReasonPhrase()
+                {
+                    return e.getMessage();
+                }
+            } );
+        }
+        return responseBuilder.build();
+    }
+}
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 (file)
index 0000000..db58033
--- /dev/null
@@ -0,0 +1,98 @@
+package org.apache.archiva.rest.services.v2;/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.archiva.admin.model.RepositoryAdminException;
+import org.apache.archiva.admin.model.beans.RedbackRuntimeConfiguration;
+import org.apache.archiva.admin.model.runtime.RedbackRuntimeConfigurationAdmin;
+import org.apache.archiva.components.rest.model.PagedResult;
+import org.apache.archiva.components.rest.model.PropertyEntry;
+import org.apache.archiva.rest.api.model.v2.BeanInformation;
+import org.apache.archiva.rest.api.model.v2.CacheConfiguration;
+import org.apache.archiva.rest.api.model.v2.LdapConfiguration;
+import org.apache.archiva.rest.api.model.v2.SecurityConfiguration;
+import org.apache.archiva.rest.api.services.v2.ArchivaRestServiceException;
+import org.apache.archiva.rest.api.services.v2.ErrorMessage;
+import org.apache.archiva.rest.api.services.v2.SecurityConfigurationService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import javax.inject.Inject;
+import java.util.List;
+
+import static org.apache.archiva.rest.services.v2.ErrorKeys.REPOSITORY_ADMIN_ERROR;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+@Service("v2.defaultSecurityConfigurationService")
+public class DefaultSecurityConfigurationService implements SecurityConfigurationService
+{
+    private static final Logger log = LoggerFactory.getLogger( DefaultSecurityConfigurationService.class );
+
+    @Inject
+    private RedbackRuntimeConfigurationAdmin redbackRuntimeConfigurationAdmin;
+
+    @Override
+    public SecurityConfiguration getConfiguration( ) throws ArchivaRestServiceException
+    {
+        try
+        {
+            RedbackRuntimeConfiguration redbackRuntimeConfiguration =
+                redbackRuntimeConfigurationAdmin.getRedbackRuntimeConfiguration();
+
+            log.debug( "getRedbackRuntimeConfiguration -> {}", redbackRuntimeConfiguration );
+
+            return SecurityConfiguration.ofRedbackConfiguration( redbackRuntimeConfiguration );
+        }
+        catch ( RepositoryAdminException e )
+        {
+            throw new ArchivaRestServiceException( ErrorMessage.of( REPOSITORY_ADMIN_ERROR ) );
+        }
+    }
+
+    @Override
+    public PagedResult<PropertyEntry> getConfigurationProperties( String searchTerm, Integer offset, Integer limit, List<String> orderBy, String order ) throws ArchivaRestServiceException
+    {
+        return null;
+    }
+
+    @Override
+    public LdapConfiguration getLdapConfiguration( ) throws ArchivaRestServiceException
+    {
+        return null;
+    }
+
+    @Override
+    public CacheConfiguration getCacheConfiguration( ) throws ArchivaRestServiceException
+    {
+        return null;
+    }
+
+    @Override
+    public List<BeanInformation> getAvailableUserManagers( ) throws ArchivaRestServiceException
+    {
+        return null;
+    }
+
+    @Override
+    public List<BeanInformation> getAvailableRbacManagers( ) throws ArchivaRestServiceException
+    {
+        return null;
+    }
+}
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 (file)
index 0000000..85e9c5c
--- /dev/null
@@ -0,0 +1,26 @@
+package org.apache.archiva.rest.services.v2;/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public interface ErrorKeys
+{
+    public static final String REPOSITORY_ADMIN_ERROR = "a.repositoryadmin.error";
+
+}
index 7d1f847b3b4ae5f05e044774eab45d381eb70d3f..910fb7e9a348f8b9f1601e5e4e639f1c0e1be8a8 100644 (file)
   <context:component-scan
       base-package="org.apache.archiva.rest.services,org.apache.archiva.redback.rest.services"/>
 
+  <!-- CXF OpenApiFeature -->
+  <bean id="archivaOpenApiFeature" class="org.apache.cxf.jaxrs.openapi.OpenApiFeature">
+    <property name="scanKnownConfigLocations" value="false"/>
+    <property name="configLocation" value="archiva/openapi-configuration.yaml"/>
+    <property name="scan" value="false"/>
+    <property name="useContextBasedConfig" value="true"/>
+    <!-- <property name="scannerClass" value="io.swagger.v3.jaxrs2.integration.JaxrsApplicationScanner"/> -->
+  </bean>
+
+
   <bean id="jsonProvider" class="com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider">
     <property name="mapper" ref="redbackJacksonJsonMapper"/>
   </bean>
 
+  <bean id="v2.jsonProvider" class="com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider">
+    <property name="mapper" ref="v2.redbackJacksonJsonMapper"/>
+  </bean>
+
   <bean id="xmlProvider" class="com.fasterxml.jackson.jaxrs.xml.JacksonJaxbXMLProvider">
     <property name="mapper" ref="redbackJacksonXMLMapper"/>
   </bean>
 
   <bean id="redbackJacksonJsonMapper" class="com.fasterxml.jackson.databind.ObjectMapper" />
+  <bean id="v2.redbackJacksonJsonMapper" class="com.fasterxml.jackson.databind.ObjectMapper" >
+  </bean>
   <bean id="redbackJacksonXMLMapper" class="com.fasterxml.jackson.dataformat.xml.XmlMapper" />
 
 
 
   </jaxrs:server>
 
+
+  <jaxrs:server name="v2.archiva" address="/v2/archiva" >
+
+    <jaxrs:providers>
+      <ref bean="v2.jsonProvider" />
+      <ref bean="bearerAuthInterceptor#rest"/>
+      <ref bean="permissionInterceptor#rest"/>
+      <ref bean="requestValidationInterceptor#rest" />
+      <ref bean="v2.archivaRestServiceExceptionMapper"/>
+      <ref bean="threadLocalUserCleaner#rest" />
+    </jaxrs:providers>
+
+    <jaxrs:serviceBeans>
+      <ref bean="v2.defaultSecurityConfigurationService" />
+    </jaxrs:serviceBeans>
+
+    <jaxrs:features>
+      <ref bean="archivaOpenApiFeature" />
+    </jaxrs:features>
+
+  </jaxrs:server>
+
+
   <bean name="browse#versionMetadata" class="org.apache.archiva.components.cache.ehcache.EhcacheCache"
         init-method="initialize">
     <property name="diskPersistent" value="false"/>
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 (file)
index 0000000..06d6f74
--- /dev/null
@@ -0,0 +1,517 @@
+package org.apache.archiva.rest.services.v2;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import io.restassured.RestAssured;
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.config.ObjectMapperConfig;
+import io.restassured.config.RestAssuredConfig;
+import io.restassured.path.json.mapper.factory.Jackson2ObjectMapperFactory;
+import io.restassured.response.Response;
+import io.restassured.specification.RequestSpecification;
+import org.apache.archiva.redback.integration.security.role.RedbackRoleConstants;
+import org.apache.archiva.redback.rest.services.BaseSetup;
+import org.apache.archiva.redback.role.RoleManager;
+import org.apache.archiva.redback.role.RoleManagerException;
+import org.apache.archiva.redback.users.User;
+import org.apache.archiva.redback.users.UserManager;
+import org.apache.archiva.redback.users.UserManagerException;
+import org.apache.archiva.redback.users.UserNotFoundException;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.SystemUtils;
+import org.apache.cxf.transport.servlet.CXFServlet;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.junit.jupiter.api.Tag;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.context.ContextLoaderListener;
+
+import java.lang.reflect.Type;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.LocalTime;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static io.restassured.RestAssured.*;
+import static io.restassured.http.ContentType.JSON;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+/**
+ * Native REST tests do not use the JAX-RS client and can be used with a remote
+ * REST API service. The tests
+ *
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+@Tag( "rest-native" )
+@Tag( "rest-v2" )
+public abstract class AbstractNativeRestServices
+{
+    private AtomicReference<Path> projectDir = new AtomicReference<>();
+    private AtomicReference<Path> appServerBase = new AtomicReference<>( );
+    private AtomicReference<Path> basePath = new AtomicReference<>( );
+
+    public static final int STOPPED = 0;
+    public static final int STOPPING = 1;
+    public static final int STARTING = 2;
+    public static final int STARTED = 3;
+    public static final int ERROR = 4;
+    private final boolean startServer;
+    private final String serverPort;
+    private final String baseUri;
+
+    private RequestSpecification requestSpec;
+    protected Logger log = LoggerFactory.getLogger( getClass( ) );
+
+    private static AtomicReference<Server> server = new AtomicReference<>( );
+    private static AtomicReference<ServerConnector> serverConnector = new AtomicReference<>( );
+    private static AtomicInteger serverStarted = new AtomicInteger( STOPPED );
+    private UserManager userManager;
+    private RoleManager roleManager;
+
+    private final boolean remoteService;
+
+    private String adminToken;
+    private String adminRefreshToken;
+
+
+    public AbstractNativeRestServices( )
+    {
+        this.startServer = BaseSetup.startServer( );
+        this.serverPort = BaseSetup.getServerPort( );
+        this.baseUri = BaseSetup.getBaseUri( );
+
+        if ( startServer )
+        {
+            this.remoteService = false;
+        } else {
+            this.remoteService = true;
+        }
+    }
+
+    protected abstract String getServicePath( );
+
+    protected String getSpringConfigLocation( )
+    {
+        return "classpath*:META-INF/spring-context.xml,classpath:META-INF/spring-context-native-test.xml";
+    }
+
+    protected RequestSpecification getRequestSpec( )
+    {
+        return this.requestSpec;
+    }
+
+    protected String getContextRoot( )
+    {
+        return "/api";
+    }
+
+
+    protected String getServiceBasePath( )
+    {
+        return "/v2/archiva";
+    }
+
+    protected String getRedbackServiceBasePath( )
+    {
+        return "/v2/redback";
+    }
+
+
+    protected String getBasePath( )
+    {
+        return new StringBuilder( )
+            .append( getContextRoot( ) )
+            .append( getServiceBasePath( ) )
+            .append( getServicePath( ) ).toString( );
+    }
+
+    /**
+     * Returns the server that was started, or null if not initialized before.
+     *
+     * @return
+     */
+    public Server getServer( )
+    {
+        return this.server.get( );
+    }
+
+    public int getServerPort( )
+    {
+        ServerConnector connector = serverConnector.get( );
+        if ( connector != null )
+        {
+            return connector.getLocalPort( );
+        }
+        else
+        {
+            return 0;
+        }
+    }
+
+    /**
+     * Returns true, if the server does exist and is running.
+     *
+     * @return true, if server does exist and is running.
+     */
+    public boolean isServerRunning( )
+    {
+        return serverStarted.get( ) == STARTED && this.server.get( ) != null && this.server.get( ).isRunning( );
+    }
+
+    private UserManager getUserManager( )
+    {
+        if ( this.userManager == null )
+        {
+            UserManager userManager = ContextLoaderListener.getCurrentWebApplicationContext( )
+                .getBean( "userManager#default", UserManager.class );
+            assertNotNull( userManager );
+            this.userManager = userManager;
+        }
+        return this.userManager;
+    }
+
+    private RoleManager getRoleManager( )
+    {
+        if ( this.roleManager == null )
+        {
+            RoleManager roleManager = ContextLoaderListener.getCurrentWebApplicationContext( )
+                .getBean( "roleManager", RoleManager.class );
+            assertNotNull( roleManager );
+            this.roleManager = roleManager;
+        }
+        return this.roleManager;
+    }
+
+    protected String getAdminPwd( )
+    {
+        return BaseSetup.getAdminPwd( );
+    }
+
+    protected String getAdminUser( )
+    {
+        return RedbackRoleConstants.ADMINISTRATOR_ACCOUNT_NAME;
+    }
+
+    private void setupAdminUser( ) throws UserManagerException, RoleManagerException
+    {
+
+        UserManager um = getUserManager( );
+
+        User adminUser = null;
+        try
+        {
+            adminUser = um.findUser( getAdminUser( ) );
+        }
+        catch ( UserNotFoundException e )
+        {
+            // ignore
+        }
+        adminUser = um.createUser( getAdminUser( ), "Administrator", "admin@local.home" );
+        adminUser.setUsername( getAdminUser( ) );
+        adminUser.setPassword( getAdminPwd( ) );
+        adminUser.setFullName( "the admin user" );
+        adminUser.setEmail( "toto@toto.fr" );
+        adminUser.setPermanent( true );
+        adminUser.setValidated( true );
+        adminUser.setLocked( false );
+        adminUser.setPasswordChangeRequired( false );
+        if ( adminUser == null )
+        {
+            um.addUser( adminUser );
+        }
+        else
+        {
+            um.updateUser( adminUser, false );
+        }
+        getRoleManager( ).assignRole( "system-administrator", adminUser.getUsername( ) );
+    }
+
+    protected Path getProjectDirectory() {
+        if ( projectDir.get()==null) {
+            String propVal = System.getProperty("mvn.project.base.dir");
+            Path newVal;
+            if (StringUtils.isEmpty(propVal)) {
+                newVal = Paths.get("").toAbsolutePath();
+            } else {
+                newVal = Paths.get(propVal).toAbsolutePath();
+            }
+            projectDir.compareAndSet(null, newVal);
+        }
+        return projectDir.get();
+    }
+
+    public Path getBasedir()
+    {
+        if (basePath.get()==null) {
+            String baseDir = System.getProperty( "basedir" );
+            final Path baseDirPath;
+            if (StringUtils.isNotEmpty( baseDir ))  {
+                baseDirPath = Paths.get( baseDir );
+            } else {
+                baseDirPath = getProjectDirectory( );
+            }
+            basePath.compareAndSet( null, baseDirPath );
+        }
+        return basePath.get( );
+    }
+
+    Path getAppserverBase() {
+        if (appServerBase.get()==null) {
+            String basePath = System.getProperty( "appserver.base" );
+            final Path appserverPath;
+            if (StringUtils.isNotEmpty( basePath )) {
+                appserverPath = Paths.get( basePath ).toAbsolutePath( );
+            } else {
+                appserverPath = getBasedir( ).resolve( "target" ).resolve( "appserver-base-" + LocalTime.now( ).toSecondOfDay( ) );
+            }
+            appServerBase.compareAndSet( null, appserverPath );
+        }
+        return appServerBase.get();
+    }
+
+    private void removeAppsubFolder( Path appServerBase, String folder )
+        throws Exception
+    {
+        Path directory = appServerBase.resolve( folder );
+        if ( Files.exists(directory) )
+        {
+            org.apache.archiva.common.utils.FileUtils.deleteDirectory( directory );
+        }
+    }
+
+    public void startServer( )
+        throws Exception
+    {
+        if ( serverStarted.compareAndSet( STOPPED, STARTING ) )
+        {
+            try
+            {
+                log.info( "Starting server" );
+                Path appServerBase = getAppserverBase( );
+
+                removeAppsubFolder(appServerBase, "jcr");
+                removeAppsubFolder(appServerBase, "conf");
+                removeAppsubFolder(appServerBase, "data");
+
+
+                Server myServer = new Server( );
+                this.server.set( myServer );
+                this.serverConnector.set( new ServerConnector( myServer, new HttpConnectionFactory( ) ) );
+                myServer.addConnector( serverConnector.get( ) );
+
+                ServletHolder servletHolder = new ServletHolder( new CXFServlet( ) );
+                ServletContextHandler context = new ServletContextHandler( ServletContextHandler.SESSIONS );
+                context.setResourceBase( SystemUtils.JAVA_IO_TMPDIR );
+                context.setSessionHandler( new SessionHandler( ) );
+                context.addServlet( servletHolder, getContextRoot( ) + "/*" );
+                context.setInitParameter( "contextConfigLocation", getSpringConfigLocation( ) );
+                context.addEventListener( new ContextLoaderListener( ) );
+
+                getServer( ).setHandler( context );
+                getServer( ).start( );
+
+                if ( log.isDebugEnabled( ) )
+                {
+                    log.debug( "Jetty dump: {}", getServer( ).dump( ) );
+                }
+
+                setupAdminUser( );
+                log.info( "Started server on port {}", getServerPort( ) );
+                serverStarted.set( STARTED );
+            }
+            finally
+            {
+                // In case, if the last statement was not reached
+                serverStarted.compareAndSet( STARTING, ERROR );
+            }
+        }
+
+    }
+
+    public void stopServer( )
+        throws Exception
+    {
+        if ( this.serverStarted.compareAndSet( STARTED, STOPPING ) )
+        {
+            try
+            {
+                final Server myServer = getServer( );
+                if ( myServer != null )
+                {
+                    log.info( "Stopping server" );
+                    myServer.stop( );
+                }
+                serverStarted.set( STOPPED );
+            }
+            finally
+            {
+                serverStarted.compareAndSet( STOPPING, ERROR );
+            }
+        }
+        else
+        {
+            log.error( "Serer is not in STARTED state!" );
+        }
+    }
+
+
+    protected void setupNative( ) throws Exception
+    {
+        if ( this.startServer )
+        {
+            startServer( );
+        }
+
+        if ( StringUtils.isNotEmpty( serverPort ) )
+        {
+            RestAssured.port = Integer.parseInt( serverPort );
+        }
+        else
+        {
+            RestAssured.port = getServerPort( );
+        }
+        if ( StringUtils.isNotEmpty( baseUri ) )
+        {
+            RestAssured.baseURI = baseUri;
+        }
+        else
+        {
+            RestAssured.baseURI = "http://localhost";
+        }
+        String basePath = getBasePath( );
+        this.requestSpec = getRequestSpecBuilder( ).build( );
+        RestAssured.basePath = basePath;
+        RestAssured.config = RestAssuredConfig.config().objectMapperConfig(new ObjectMapperConfig().jackson2ObjectMapperFactory(
+            new Jackson2ObjectMapperFactory() {
+                @Override
+                public ObjectMapper create( Type cls, String charset) {
+                    ObjectMapper om = new ObjectMapper().findAndRegisterModules();
+                    om.configure( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+                    om.setPropertyNamingStrategy( PropertyNamingStrategy.SNAKE_CASE );
+                    return om;
+                }
+
+            }
+        ));
+    }
+
+    protected RequestSpecBuilder getRequestSpecBuilder( ) {
+        return getRequestSpecBuilder( null );
+    }
+
+    protected RequestSpecBuilder getRequestSpecBuilder( String basePath )
+    {
+        String myBasePath = basePath == null ? getBasePath( ) : basePath;
+        return new RequestSpecBuilder( ).setBaseUri( baseURI )
+            .setPort( port )
+            .setBasePath( myBasePath )
+            .addHeader( "Origin", RestAssured.baseURI + ":" + RestAssured.port );
+    }
+
+    protected RequestSpecBuilder getAuthRequestSpecBuilder( )
+    {
+        return new RequestSpecBuilder( ).setBaseUri( baseURI )
+            .setPort( port )
+            .setBasePath( new StringBuilder( )
+                .append( getContextRoot( ) )
+                .append( getRedbackServiceBasePath() ).append("/auth").toString() )
+            .addHeader( "Origin", RestAssured.baseURI + ":" + RestAssured.port );
+    }
+
+    protected RequestSpecification getRequestSpec( String bearerToken )
+    {
+        return getRequestSpecBuilder( ).addHeader( "Authorization", "Bearer " + bearerToken ).build( );
+    }
+
+    protected RequestSpecification getRequestSpec( String bearerToken, String path)
+    {
+        return getRequestSpecBuilder( path  ).addHeader( "Authorization", "Bearer " + bearerToken ).build( );
+    }
+
+    protected void shutdownNative( ) throws Exception
+    {
+        if (startServer)
+        {
+            stopServer( );
+        }
+    }
+
+    protected org.apache.archiva.redback.rest.api.model.User addRemoteUser(String userid, String password, String fullName, String mail) {
+
+        return null;
+    }
+
+    protected void initAdminToken() {
+        Map<String, Object> jsonAsMap = new HashMap<>();
+        jsonAsMap.put( "grant_type", "authorization_code" );
+        jsonAsMap.put("user_id", getAdminUser());
+        jsonAsMap.put("password", getAdminPwd() );
+        Response result = given( ).spec( getAuthRequestSpecBuilder().build() )
+            .contentType( JSON )
+            .body( jsonAsMap )
+            .when( ).post( "/authenticate").then( ).statusCode( 200 )
+            .extract( ).response( );
+        this.adminToken = result.body( ).jsonPath( ).getString( "access_token" );
+        this.adminRefreshToken = result.body( ).jsonPath( ).getString( "refresh_token" );
+    }
+
+    protected String getUserToken(String userId, String password) {
+        Map<String, Object> jsonAsMap = new HashMap<>();
+        jsonAsMap.put( "grant_type", "authorization_code" );
+        jsonAsMap.put("user_id", userId);
+        jsonAsMap.put("password", password );
+        Response result = given( ).spec( getAuthRequestSpecBuilder().build() )
+            .contentType( JSON )
+            .body( jsonAsMap )
+            .when( ).post( "/authenticate").prettyPeek().then( ).statusCode( 200 )
+            .extract( ).response( );
+        result.getBody( ).prettyPrint( );
+        return result.body( ).jsonPath( ).getString( "access_token" );
+    }
+    protected String getAdminToken()  {
+        if (this.adminToken == null) {
+            initAdminToken();
+        }
+        return this.adminToken;
+    }
+
+
+    protected String getAdminRefreshToken()  {
+        if (this.adminRefreshToken == null) {
+            initAdminToken();
+        }
+        return this.adminRefreshToken;
+    }
+
+    public boolean isRemoteService() {
+        return this.remoteService;
+    }
+}
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 (file)
index 0000000..eff49c1
--- /dev/null
@@ -0,0 +1,82 @@
+package org.apache.archiva.rest.services.v2;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import io.restassured.response.Response;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import static io.restassured.RestAssured.given;
+import static io.restassured.http.ContentType.JSON;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+@TestInstance( TestInstance.Lifecycle.PER_CLASS )
+@Tag( "rest-native" )
+@TestMethodOrder( MethodOrderer.Random.class )
+@DisplayName( "Native REST tests for V2 SecurityConfigurationService" )
+public class NativeSecurityConfigurationServiceTest extends AbstractNativeRestServices
+{
+    @Override
+    protected String getServicePath( )
+    {
+        return "/security";
+    }
+
+    @BeforeAll
+    void setup( ) throws Exception
+    {
+        super.setupNative( );
+    }
+
+    @AfterAll
+    void destroy( ) throws Exception
+    {
+        super.shutdownNative( );
+    }
+
+    @Test
+    void testGetConfiguration() {
+        String token = getAdminToken( );
+            Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .get( "config" )
+                .prettyPeek()
+                .then( ).statusCode( 200 ).extract( ).response( );
+        assertNotNull( response );
+        assertEquals( "jpa", response.getBody( ).jsonPath( ).getString( "active_user_managers[0]" ) );
+        assertEquals( "jpa", response.getBody( ).jsonPath( ).getString( "active_rbac_managers[0]" ) );
+        assertEquals( "memory", response.getBody( ).jsonPath( ).getString( "properties.\"authentication.jwt.keystoreType\"" ) );
+        assertEquals("10",response.getBody( ).jsonPath( ).getString( "properties.\"security.policy.allowed.login.attempt\""));
+        assertTrue( response.getBody( ).jsonPath( ).getBoolean( "user_cache_enabled" ) );
+        assertFalse( response.getBody( ).jsonPath( ).getBoolean( "ldap_active" ) );
+    }
+
+}
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 (file)
index 0000000..79379ad
--- /dev/null
@@ -0,0 +1,143 @@
+<?xml version="1.0"?>
+
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xmlns:tx="http://www.springframework.org/schema/tx"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+           http://www.springframework.org/schema/context
+           http://www.springframework.org/schema/context/spring-context-3.0.xsd
+
+            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"
+       default-lazy-init="true">
+
+  <context:annotation-config/>
+  <context:component-scan
+      base-package="org.apache.archiva.redback.configuration,org.apache.archiva.redback.keys,org.apache.archiva.rest.services.utils,org.apache.archiva.repository.content"/>
+  
+  <bean name="scheduler" class="org.apache.archiva.components.scheduler.DefaultScheduler">
+    <property name="properties">
+      <props>
+        <prop key="org.quartz.scheduler.instanceName">scheduler1</prop>
+        <prop key="org.quartz.threadPool.class">org.quartz.simpl.SimpleThreadPool</prop>
+        <prop key="org.quartz.threadPool.threadCount">2</prop>
+        <prop key="org.quartz.threadPool.threadPriority">4</prop>
+        <prop key="org.quartz.jobStore.class">org.quartz.simpl.RAMJobStore</prop>
+      </props>
+    </property>
+  </bean>
+
+  <!-- wire up more basic configuration so it doesn't overwrite any config files -->
+  <bean name="archivaConfiguration#default" class="org.apache.archiva.configuration.DefaultArchivaConfiguration">
+    <property name="registry" ref="registry#default"/>
+  </bean>
+
+  <alias name="archivaConfiguration#default" alias="archivaConfiguration"/>
+
+  <bean name="registry#default" class="org.apache.archiva.components.registry.commons.CommonsConfigurationRegistry">
+    <property name="initialConfiguration">
+      <value>
+        <![CDATA[
+        <configuration>
+          <system/>
+          <xml fileName="${appserver.base}/conf/archiva.xml" config-forceCreate="true"
+               config-optional="true"
+               config-name="org.apache.archiva.base" config-at="org.apache.archiva"/>
+          <properties fileName="${basedir}/src/test/resources/security.properties" config-optional="true"
+                      config-at="org.apache.archiva.redback"/>
+
+        </configuration>
+        ]]>
+      </value>
+    </property>
+  </bean>
+
+  <bean name="taskQueueExecutor#repository-scanning"
+        class="org.apache.archiva.components.taskqueue.execution.ThreadedTaskQueueExecutor" lazy-init="false">
+    <property name="name" value="repository-scanning"/>
+    <property name="executor" ref="taskExecutor#repository-scanning"/>
+    <property name="queue" ref="taskQueue#repository-scanning"/>
+  </bean>
+
+  <!--
+  <bean id="repository" class="org.apache.jackrabbit.core.RepositoryImpl" destroy-method="shutdown">
+    <constructor-arg ref="config"/>
+  </bean>
+
+  <bean id="config" class="org.apache.jackrabbit.core.config.RepositoryConfig" factory-method="create">
+    <constructor-arg value="${basedir}/src/test/repository.xml"/>
+    <constructor-arg value="${appserver.base}/jcr"/>
+  </bean>
+  -->
+
+  <bean name="commons-configuration" class="org.apache.archiva.components.registry.commons.CommonsConfigurationRegistry"
+        init-method="initialize">
+    <property name="initialConfiguration">
+      <value>
+        <![CDATA[
+        <configuration>
+          <system/>
+          <properties fileName="${basedir}/src/test/resources/security.properties" config-optional="true"
+                      config-at="org.apache.archiva.redback"/>
+        </configuration>
+        ]]>
+      </value>
+    </property>
+  </bean>
+
+
+  <alias name="redbackRuntimeConfigurationAdmin#default" alias="userConfiguration#default"/>
+
+  <alias name="authorizer#rbac" alias="authorizer#default"/>
+
+  <alias name="userManager#configurable" alias="userManager#default"/>
+
+  <!-- ***
+ JPA settings
+ *** -->
+  <bean name="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
+
+    <property name="persistenceXmlLocation" value="classpath:META-INF/persistence-hsqldb.xml" />
+    <property name="jpaPropertyMap">
+      <map>
+        <entry key="openjpa.ConnectionURL" value="jdbc:hsqldb:mem:redback_database" />
+        <entry key="openjpa.ConnectionDriverName" value="org.hsqldb.jdbcDriver" />
+        <entry key="openjpa.ConnectionUserName" value="sa" />
+        <entry key="openjpa.ConnectionPassword" value="" />
+        <entry key="openjpa.Log" value="${openjpa.Log:DefaultLevel=INFO,Runtime=ERROR,Tool=ERROR,SQL=ERROR,Schema=ERROR,MetaData=ERROR}" />
+        <entry key="openjpa.jdbc.SynchronizeMappings" value="buildSchema(ForeignKeys=true)" />
+        <entry key="openjpa.jdbc.MappingDefaults"
+               value="ForeignKeyDeleteAction=restrict,JoinForeignKeyDeleteAction=restrict"/>
+      </map>
+    </property>
+
+  </bean>
+
+  <bean name="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager" >
+    <property name="entityManagerFactory" ref="entityManagerFactory" />
+  </bean>
+
+  <tx:annotation-driven />
+  <!-- ***
+     End of JPA settings
+     *** -->
+</beans>
\ No newline at end of file
index 8e0fce6e07ce836359475ccacbab843d9da6792c..c0414be389ec71d8a9e4b5dff8c7b7383baca8b3 100644 (file)
   <loggers>
 
     <logger name="jaxrs" level="info" />
-    <logger name="org.apache.cxf" level="info" />
+    <logger name="org.apache.cxf" level="debug" />
     <logger name="org.apache.archiva" level="debug" />
     <logger name="org.apache.archiva.redback" level="debug"/>
     <logger name="com.fasterxml.jackson" level="info" />
-    <logger name="org.apache.archiva.components.registry.commons" level="error" />
+    <logger name="org.apache.archiva.components" level="error" />
 
     <logger name="JPOX" level="error"/>
     <logger name="org.apache.archiva.rest.services" level="info"/>
diff --git a/pom.xml b/pom.xml
index 307466d0239afb826a1b87a458e81ef907e37c76..82b0280c13231dfad92c866e13ace1a543a4c366 100644 (file)
--- a/pom.xml
+++ b/pom.xml
         <artifactId>cxf-rt-features-logging</artifactId>
         <version>${cxf.version}</version>
       </dependency>
+      <dependency>
+        <groupId>org.apache.cxf</groupId>
+        <artifactId>cxf-rt-rs-service-description-openapi-v3</artifactId>
+        <version>${cxf.version}</version>
+      </dependency>
+
+      <dependency>
+        <groupId>io.swagger.core.v3</groupId>
+        <artifactId>swagger-core</artifactId>
+        <scope>compile</scope>
+        <version>${io.swagger.version}</version>
+        <exclusions>
+          <exclusion>
+            <groupId>javax.ws.rs</groupId>
+            <artifactId>jsr311-api</artifactId>
+          </exclusion>
+        </exclusions>
+      </dependency>
+      <dependency>
+        <groupId>io.swagger.core.v3</groupId>
+        <artifactId>swagger-jaxrs2</artifactId>
+        <version>${io.swagger.version}</version>
+        <exclusions>
+          <exclusion>
+            <groupId>javax.ws.rs</groupId>
+            <artifactId>jsr311-api</artifactId>
+          </exclusion>
+        </exclusions>
+      </dependency>
+      <dependency>
+        <groupId>io.swagger.core.v3</groupId>
+        <artifactId>swagger-annotations</artifactId>
+        <version>${io.swagger.version}</version>
+        <exclusions>
+          <exclusion>
+            <groupId>javax.ws.rs</groupId>
+            <artifactId>jsr311-api</artifactId>
+          </exclusion>
+        </exclusions>
+      </dependency>
+
 
       <dependency>
         <groupId>org.apache.archiva</groupId>
         <version>${maven.resolver.version}</version>
       </dependency>
 
+      <dependency>
+        <groupId>org.apache.archiva.components</groupId>
+        <artifactId>archiva-components-rest-util</artifactId>
+        <version>${archiva.comp.version}</version>
+      </dependency>
       <dependency>
         <groupId>org.apache.archiva.components</groupId>
         <artifactId>archiva-components-expression-evaluator</artifactId>
         <version>${archiva.comp.version}</version>
       </dependency>
-
       <dependency>
         <groupId>org.apache.archiva.components</groupId>
         <artifactId>archiva-components-spring-taskqueue</artifactId>
       </dependency>
 
 
+
       <!-- Transitive dependency - fixing version -->
       <dependency>
         <groupId>com.google.guava</groupId>
         <scope>test</scope>
       </dependency>
 
+      <dependency>
+        <groupId>io.rest-assured</groupId>
+        <artifactId>rest-assured</artifactId>
+        <version>${rest-assured.version}</version>
+        <scope>test</scope>
+      </dependency>
 
       <!-- JUNIT 5 -->
       <dependency>
         <version>${cxf.version}</version>
       </dependency>
 
-      <dependency>
-        <groupId>io.swagger.core.v3</groupId>
-        <artifactId>swagger-core</artifactId>
-        <scope>compile</scope>
-        <version>${io.swagger.version}</version>
-        <exclusions>
-          <exclusion>
-            <groupId>javax.ws.rs</groupId>
-            <artifactId>jsr311-api</artifactId>
-          </exclusion>
-        </exclusions>
-      </dependency>
-      <dependency>
-        <groupId>io.swagger.core.v3</groupId>
-        <artifactId>swagger-jaxrs2</artifactId>
-        <version>${io.swagger.version}</version>
-        <exclusions>
-          <exclusion>
-            <groupId>javax.ws.rs</groupId>
-            <artifactId>jsr311-api</artifactId>
-          </exclusion>
-        </exclusions>
-      </dependency>
-      <dependency>
-        <groupId>io.swagger.core.v3</groupId>
-        <artifactId>swagger-annotations</artifactId>
-        <version>${io.swagger.version}</version>
-        <exclusions>
-          <exclusion>
-            <groupId>javax.ws.rs</groupId>
-            <artifactId>jsr311-api</artifactId>
-          </exclusion>
-        </exclusions>
-      </dependency>
-
-
-
-
     </dependencies>
   </dependencyManagement>