You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by rn...@apache.org on 2023/12/07 13:44:50 UTC
(couchdb) 01/01: WIP
This is an automated email from the ASF dual-hosted git repository.
rnewson pushed a commit to branch nouveau-auth
in repository https://gitbox.apache.org/repos/asf/couchdb.git
commit 3ee019a8a43166a4e51829f2367985f49fca8be0
Author: Robert Newson <rn...@apache.org>
AuthorDate: Thu Dec 7 13:44:32 2023 +0000
WIP
---
.../apache/couchdb/nouveau/NouveauApplication.java | 8 ++++
.../apache/couchdb/nouveau/auth/CouchDBUser.java | 29 +++++++++++
.../couchdb/nouveau/core/UserAgentFilter.java | 56 ++++++++++++++++++++++
src/nouveau/src/nouveau_api.erl | 17 +++----
4 files changed, 102 insertions(+), 8 deletions(-)
diff --git a/nouveau/src/main/java/org/apache/couchdb/nouveau/NouveauApplication.java b/nouveau/src/main/java/org/apache/couchdb/nouveau/NouveauApplication.java
index 89b2b8596..5f2af45ee 100644
--- a/nouveau/src/main/java/org/apache/couchdb/nouveau/NouveauApplication.java
+++ b/nouveau/src/main/java/org/apache/couchdb/nouveau/NouveauApplication.java
@@ -17,9 +17,12 @@ import com.github.benmanes.caffeine.cache.Scheduler;
import io.dropwizard.core.Application;
import io.dropwizard.core.setup.Environment;
import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource;
+import jakarta.servlet.DispatcherType;
+import java.util.EnumSet;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ScheduledExecutorService;
import org.apache.couchdb.nouveau.core.IndexManager;
+import org.apache.couchdb.nouveau.core.UserAgentFilter;
import org.apache.couchdb.nouveau.health.AnalyzeHealthCheck;
import org.apache.couchdb.nouveau.health.IndexHealthCheck;
import org.apache.couchdb.nouveau.lucene9.Lucene9Module;
@@ -41,6 +44,11 @@ public class NouveauApplication extends Application<NouveauApplicationConfigurat
@Override
public void run(NouveauApplicationConfiguration configuration, Environment environment) throws Exception {
+ // require User-Agent: CouchDB on all requests.
+ environment
+ .servlets()
+ .addFilter("UserAgentFilter", new UserAgentFilter("CouchDB"))
+ .addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
// configure index manager
final IndexManager indexManager = new IndexManager();
diff --git a/nouveau/src/main/java/org/apache/couchdb/nouveau/auth/CouchDBUser.java b/nouveau/src/main/java/org/apache/couchdb/nouveau/auth/CouchDBUser.java
new file mode 100644
index 000000000..7fdc88b2f
--- /dev/null
+++ b/nouveau/src/main/java/org/apache/couchdb/nouveau/auth/CouchDBUser.java
@@ -0,0 +1,29 @@
+// Licensed 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.
+
+package org.apache.couchdb.nouveau.auth;
+
+import java.security.Principal;
+
+public final class CouchDBUser implements Principal {
+
+ private final String name;
+
+ public CouchDBUser(final String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+}
diff --git a/nouveau/src/main/java/org/apache/couchdb/nouveau/core/UserAgentFilter.java b/nouveau/src/main/java/org/apache/couchdb/nouveau/core/UserAgentFilter.java
new file mode 100644
index 000000000..ee534dfc1
--- /dev/null
+++ b/nouveau/src/main/java/org/apache/couchdb/nouveau/core/UserAgentFilter.java
@@ -0,0 +1,56 @@
+//
+// Licensed 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.
+
+package org.apache.couchdb.nouveau.core;
+
+import com.google.common.net.HttpHeaders;
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Objects;
+import org.eclipse.jetty.http.HttpStatus;
+
+public final class UserAgentFilter implements Filter {
+
+ private final String requiredUserAgent;
+
+ public UserAgentFilter(final String requiredUserAgent) {
+ this.requiredUserAgent = Objects.requireNonNull(requiredUserAgent);
+ }
+
+ @Override
+ public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
+ throws IOException, ServletException {
+ if (request instanceof HttpServletRequest) {
+ final String userAgent = ((HttpServletRequest) request).getHeader(HttpHeaders.USER_AGENT);
+
+ if (!requiredUserAgent.equals(userAgent)) {
+ HttpServletResponse httpResponse = (HttpServletResponse) response;
+ httpResponse.setStatus(HttpStatus.FORBIDDEN_403);
+ httpResponse.setContentType("application/json");
+ httpResponse
+ .getWriter()
+ .print(String.format(
+ "{\"error\": \"forbidden\", \"reason\": \"User-Agent must be %s\"}\n",
+ requiredUserAgent));
+ } else {
+ chain.doFilter(request, response);
+ }
+ }
+ }
+}
diff --git a/src/nouveau/src/nouveau_api.erl b/src/nouveau/src/nouveau_api.erl
index 57de204f2..a2abf272e 100644
--- a/src/nouveau/src/nouveau_api.erl
+++ b/src/nouveau/src/nouveau_api.erl
@@ -34,6 +34,7 @@
]).
-define(JSON_CONTENT_TYPE, {"Content-Type", "application/json"}).
+-define(USER_AGENT, {"User-Agent", "CouchDB"}).
analyze(Text, Analyzer) when
is_binary(Text), is_binary(Analyzer)
@@ -305,24 +306,24 @@ errors(Body) ->
Json = jiffy:decode(Body, [return_maps]),
maps:get(<<"errors">>, Json).
-send_if_enabled(Url, Header, Method) ->
- send_if_enabled(Url, Header, Method, []).
+send_if_enabled(Url, Headers, Method) ->
+ send_if_enabled(Url, Headers, Method, []).
-send_if_enabled(Url, Header, Method, Body) ->
- send_if_enabled(Url, Header, Method, Body, []).
+send_if_enabled(Url, Headers, Method, Body) ->
+ send_if_enabled(Url, Headers, Method, Body, []).
-send_if_enabled(Url, Header, Method, Body, Options) ->
+send_if_enabled(Url, Headers, Method, Body, Options) ->
case nouveau:enabled() of
true ->
- ibrowse:send_req(Url, Header, Method, Body, Options);
+ ibrowse:send_req(Url, [?USER_AGENT | Headers], Method, Body, Options);
false ->
{error, nouveau_not_enabled}
end.
-send_direct_if_enabled(ConnPid, Url, Header, Method, Body, Options) ->
+send_direct_if_enabled(ConnPid, Url, Headers, Method, Body, Options) ->
case nouveau:enabled() of
true ->
- ibrowse:send_req_direct(ConnPid, Url, Header, Method, Body, Options);
+ ibrowse:send_req_direct(ConnPid, Url, [?USER_AGENT | Headers], Method, Body, Options);
false ->
{error, nouveau_not_enabled}
end.