You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2022/05/16 17:30:42 UTC
[sling-whiteboard] branch master updated: First draft of an OIDC RP support bundle
This is an automated email from the ASF dual-hosted git repository.
rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-whiteboard.git
The following commit(s) were added to refs/heads/master by this push:
new c7d9dd7e First draft of an OIDC RP support bundle
c7d9dd7e is described below
commit c7d9dd7edfc5a2ed8b5fe995158e79ee3b98fd2c
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Mon May 16 16:50:01 2022 +0200
First draft of an OIDC RP support bundle
---
org.apache.sling.servlets.oidc-rp/README.md | 54 ++++++++
org.apache.sling.servlets.oidc-rp/pom.xml | 109 +++++++++++++++
.../servlets/oidc_rp/OidcConnectionFinder.java | 38 ++++++
.../sling/servlets/oidc_rp/impl/Endpoints.java | 54 ++++++++
.../servlets/oidc_rp/impl/OidcCallbackServlet.java | 147 +++++++++++++++++++++
.../servlets/oidc_rp/impl/OidcConnection.java | 27 ++++
.../oidc_rp/impl/OidcConnectionFinderImpl.java | 90 +++++++++++++
.../servlets/oidc_rp/impl/OidcConnectionImpl.java | 72 ++++++++++
.../oidc_rp/impl/OidcConnectionPersister.java | 24 ++++
.../oidc_rp/impl/OidcEntryPointServlet.java | 90 +++++++++++++
.../sling/servlets/oidc_rp/package-info.java | 20 +++
11 files changed, 725 insertions(+)
diff --git a/org.apache.sling.servlets.oidc-rp/README.md b/org.apache.sling.servlets.oidc-rp/README.md
new file mode 100644
index 00000000..c4694add
--- /dev/null
+++ b/org.apache.sling.servlets.oidc-rp/README.md
@@ -0,0 +1,54 @@
+# Apache Sling OpenID Connect Relying Party support bundle
+
+## Sling Starter Deployment
+
+```diff
+diff --git a/src/main/features/app/starter.json b/src/main/features/app/starter.json
+index 9c9231f..18c1586 100644
+--- a/src/main/features/app/starter.json
++++ b/src/main/features/app/starter.json
+@@ -3,6 +3,34 @@
+ {
+ "id":"org.apache.sling:org.apache.sling.starter.content:1.0.12",
+ "start-order":"20"
++ },
++ {
++ "id":"com.nimbusds:oauth2-oidc-sdk:9.35",
++ "start-order":"20"
++ },
++ {
++ "id":"com.nimbusds:nimbus-jose-jwt:9.22",
++ "start-order":"20"
++ },
++ {
++ "id":"com.nimbusds:content-type:2.2",
++ "start-order":"20"
++ },
++ {
++ "id":"com.nimbusds:lang-tag:1.6",
++ "start-order":"20"
++ },
++ {
++ "id":"org.apache.servicemix.bundles:org.apache.servicemix.bundles.jcip-annotations:1.0_2",
++ "start-order":"20"
++ },
++ {
++ "id":"net.minidev:json-smart:2.4.8",
++ "start-order":"20"
++ },
++ {
++ "id":"net.minidev:accessors-smart:2.4.8",
++ "start-order":"20"
+ }
+ ]
+ }
+
+```
+
+## Whiteboard graudation TODO
+
+- bundle/package should probably be org.apache.sling.extensions.odic-rp, as the primary entry point is the Java API
+- document usage; make sure to explain this is _not_ an authentication handler
+- provide a sample content package and instructions how to use
+- review to see if we can use more of the Nimbus SDK, e.g. enpodints discovery, token parsing
+- review security best practices
diff --git a/org.apache.sling.servlets.oidc-rp/pom.xml b/org.apache.sling.servlets.oidc-rp/pom.xml
new file mode 100644
index 00000000..dd92c11a
--- /dev/null
+++ b/org.apache.sling.servlets.oidc-rp/pom.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>sling-bundle-parent</artifactId>
+ <version>47</version>
+ <relativePath/>
+ </parent>
+
+ <artifactId>org.apache.sling.servlets.oidc-rp</artifactId>
+ <version>0.1-SNAPSHOT</version>
+
+ <name>Apache Sling Servlets OIDC Relying Party</name>
+
+ <properties>
+ <sling.java.version>17</sling.java.version>
+ <project.build.outputTimestamp>1</project.build.outputTimestamp>
+ <bnd.baseline.skip>true</bnd.baseline.skip>
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>biz.aQute.bnd</groupId>
+ <artifactId>bnd-maven-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.service.component.annotations</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.service.metatype.annotations</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.annotation.versioning</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.servlets.annotations</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.jcr</groupId>
+ <artifactId>jcr</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jackrabbit</groupId>
+ <artifactId>jackrabbit-api</artifactId>
+ <version>2.0.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.api</artifactId>
+ <version>2.0.8</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.nimbusds</groupId>
+ <artifactId>oauth2-oidc-sdk</artifactId>
+ <version>9.35</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.commons.johnzon</artifactId>
+ <version>1.0.0</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/OidcConnectionFinder.java b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/OidcConnectionFinder.java
new file mode 100644
index 00000000..116505bb
--- /dev/null
+++ b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/OidcConnectionFinder.java
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+package org.apache.sling.servlets.oidc_rp;
+
+import java.net.URI;
+import java.util.Optional;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.ResourceResolver;
+
+// TODO - support more than one connection
+// TODO - bad name
+public interface OidcConnectionFinder {
+
+ // flow would be :
+ // 1. get oidc token
+ // 1a. found → all good
+ // 1b. not found → redirect to oidc entry point uri
+ // 2. after callback, redirect back to same page (how, cookie?)
+ // see https://github.com/panva/node-openid-client/issues/83
+ Optional<String> getOidcToken(ResourceResolver resolver);
+
+ URI getOidcEntryPointUri(SlingHttpServletRequest request, String redirectPath);
+}
diff --git a/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/impl/Endpoints.java b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/impl/Endpoints.java
new file mode 100644
index 00000000..f167f24c
--- /dev/null
+++ b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/impl/Endpoints.java
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+package org.apache.sling.servlets.oidc_rp.impl;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+
+import javax.json.Json;
+import javax.json.JsonObject;
+import javax.json.JsonReader;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+record Endpoints(String authorizationEndpoint, String tokenEndpoint) {
+
+ private static final Logger logger = LoggerFactory.getLogger(Endpoints.class);
+
+ // TODO - cache endpoints, move to service?
+ static Endpoints discover(String base, HttpClient client) throws IOException, InterruptedException {
+ HttpRequest discoveryRequest = HttpRequest.newBuilder()
+ .uri(URI.create(base + "/.well-known/openid-configuration"))
+ .build();
+
+ HttpResponse<byte[]> response = client.send(discoveryRequest, BodyHandlers.ofByteArray());
+ try (JsonReader reader = Json.createReader(new ByteArrayInputStream(response.body()))) {
+ JsonObject cfg = reader.readObject();
+ String authEndpoint = cfg.getJsonString("authorization_endpoint").getString();
+ String tokenEndpoint = cfg.getJsonString("token_endpoint").getString();
+ Endpoints ep = new Endpoints(authEndpoint, tokenEndpoint);
+ logger.info("Discovered auth_endpoint {} , token_endpoint {}", ep.authorizationEndpoint(), ep.tokenEndpoint());
+ return ep;
+ }
+ }
+}
\ No newline at end of file
diff --git a/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/impl/OidcCallbackServlet.java b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/impl/OidcCallbackServlet.java
new file mode 100644
index 00000000..221cd868
--- /dev/null
+++ b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/impl/OidcCallbackServlet.java
@@ -0,0 +1,147 @@
+/*
+ * 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.
+ */
+package org.apache.sling.servlets.oidc_rp.impl;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.json.Json;
+import javax.json.JsonObject;
+import javax.json.JsonReader;
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.servlets.SlingAllMethodsServlet;
+import org.apache.sling.servlets.annotations.SlingServletPaths;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.oauth2.sdk.id.State;
+import com.nimbusds.openid.connect.sdk.AuthenticationErrorResponse;
+import com.nimbusds.openid.connect.sdk.AuthenticationResponse;
+import com.nimbusds.openid.connect.sdk.AuthenticationResponseParser;
+
+@Component(service = { Servlet.class })
+@SlingServletPaths(OidcCallbackServlet.PATH)
+public class OidcCallbackServlet extends SlingAllMethodsServlet {
+
+ static final String PATH = "/system/sling/oidc/callback";
+
+ private static final long serialVersionUID = 1L;
+
+ private final OidcConnection connection;
+ private final OidcConnectionPersister persister;
+
+ static String getCallbackUri(HttpServletRequest request) {
+ return request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + PATH;
+ }
+
+ @Activate
+ public OidcCallbackServlet(@Reference OidcConnection connection, @Reference OidcConnectionPersister persister) {
+ this.connection = connection;
+ this.persister = persister;
+ }
+
+ @Override
+ protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
+ throws ServletException, IOException {
+ try {
+ StringBuffer requestURL = request.getRequestURL();
+ if ( request.getQueryString() != null )
+ requestURL.append('?').append(request.getQueryString());
+
+ AuthenticationResponse authResponse = AuthenticationResponseParser.parse(new URI(requestURL.toString()));
+ State state = (State) request.getSession().getAttribute(OidcEntryPointServlet.SESSION_ATTRIBUTE_STATE);
+ request.getSession().removeAttribute(OidcEntryPointServlet.SESSION_ATTRIBUTE_STATE);
+ if ( state == null || !authResponse.getState().equals(state))
+ throw new ServletException("Failed state check");
+
+ if ( response instanceof AuthenticationErrorResponse )
+ throw new ServletException(authResponse.toErrorResponse().getErrorObject().toString());
+
+ String authCode = authResponse.toSuccessResponse().getAuthorizationCode().getValue();
+
+ HttpClient client = HttpClient.newHttpClient();
+ Endpoints ep = Endpoints.discover(connection.baseUrl(), client);
+
+ Map<String, String> params = new HashMap<>();
+ params.put("client_id", connection.clientId());
+ params.put("client_secret", connection.clientSecret());
+ params.put("code", authCode);
+ params.put("grant_type", "authorization_code");
+ params.put("redirect_uri", getCallbackUri(request));
+
+ String payload = params.entrySet().stream()
+ .map( e -> e.getKey() + "=" +URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8))
+ .collect(Collectors.joining("&"));
+
+ HttpRequest tokenRequest = HttpRequest.newBuilder()
+ .uri(URI.create(ep.tokenEndpoint()))
+ .headers("Content-Type", "application/x-www-form-urlencoded")
+ .POST(BodyPublishers.ofString(payload))
+ .build();
+
+ HttpResponse<String> tokenResponse = client.send(tokenRequest, BodyHandlers.ofString());
+
+ String accessToken;
+ try ( JsonReader reader = Json.createReader(new StringReader(tokenResponse.body())) ) {
+ JsonObject tokenObject = reader.readObject();
+ accessToken = tokenObject.getString("access_token");
+// int expires = tokenObject.getInt("expires_in"); // TODO - this is optional
+ }
+
+ persister.persistToken(request.getResourceResolver(), accessToken);
+
+ String encodedRedirect = (String) request.getSession().getAttribute(OidcEntryPointServlet.SESSION_ATTRIBUTE_REDIRECT);
+ if ( encodedRedirect == null ) {
+ response.setStatus(HttpServletResponse.SC_OK);
+ response.addHeader("Content-Type", "text/plain");
+ response.getWriter().write("OK");
+ response.getWriter().flush();
+ response.getWriter().close();
+ } else {
+ request.getSession().removeAttribute(OidcEntryPointServlet.SESSION_ATTRIBUTE_REDIRECT);
+ String redirect = URLDecoder.decode(encodedRedirect, StandardCharsets.UTF_8);
+ response.sendRedirect(redirect);
+ }
+ } catch (ParseException | URISyntaxException | IOException e) {
+ throw new ServletException(e);
+ } catch (InterruptedException e1) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+}
diff --git a/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/impl/OidcConnection.java b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/impl/OidcConnection.java
new file mode 100644
index 00000000..73329dfa
--- /dev/null
+++ b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/impl/OidcConnection.java
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+package org.apache.sling.servlets.oidc_rp.impl;
+
+// TODO - bad name
+public interface OidcConnection {
+
+ String name();
+ String baseUrl();
+ String clientId();
+ String clientSecret();
+ String[] scopes();
+}
diff --git a/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/impl/OidcConnectionFinderImpl.java b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/impl/OidcConnectionFinderImpl.java
new file mode 100644
index 00000000..ecc21992
--- /dev/null
+++ b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/impl/OidcConnectionFinderImpl.java
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+package org.apache.sling.servlets.oidc_rp.impl;
+
+import java.net.URI;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Optional;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.servlets.oidc_rp.OidcConnectionFinder;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+@Component
+public class OidcConnectionFinderImpl implements OidcConnectionFinder, OidcConnectionPersister {
+
+ private final OidcConnection connection;
+
+ @Activate
+ public OidcConnectionFinderImpl(@Reference OidcConnection connection) {
+ this.connection = connection;
+ }
+
+ @Override
+ public Optional<String> getOidcToken(ResourceResolver resolver) {
+ // TODO - remove expired tokens
+ try {
+ User user = resolver.adaptTo(User.class);
+ Value[] tokenValue = user.getProperty(tokenPropertyPath());
+ if ( tokenValue == null )
+ return Optional.empty();
+
+ if ( tokenValue.length != 1)
+ throw new RuntimeException("Unexpected value count for token property : " + tokenValue.length);
+
+ return Optional.of(tokenValue[0].getString());
+ } catch (RepositoryException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void persistToken(ResourceResolver resolver, String tokenValue) {
+ try {
+ User currentUser = resolver.adaptTo(User.class);
+ Session session = resolver.adaptTo(Session.class);
+ currentUser.setProperty("oidc-tokens/" + connection.name() + "/token", session.getValueFactory().createValue(tokenValue));
+ session.save();
+ } catch (RepositoryException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public URI getOidcEntryPointUri(SlingHttpServletRequest request, String redirectPath) {
+ StringBuilder uri = new StringBuilder();
+ uri.append(request.getScheme()).append("://").append(request.getServerName()).append(":").append(request.getServerPort()).append(OidcEntryPointServlet.PATH);
+ if ( redirectPath != null )
+ uri.append("?redirect=").append(URLEncoder.encode(redirectPath, StandardCharsets.UTF_8));
+
+ return URI.create(uri.toString());
+ }
+
+ private String tokenPropertyPath() {
+ return "oidc-tokens/" + connection.name() + "/token";
+ }
+
+}
diff --git a/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/impl/OidcConnectionImpl.java b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/impl/OidcConnectionImpl.java
new file mode 100644
index 00000000..9a7ab472
--- /dev/null
+++ b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/impl/OidcConnectionImpl.java
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+package org.apache.sling.servlets.oidc_rp.impl;
+
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.AttributeType;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+// TODO - bad name
+@Component
+@Designate(ocd = OidcConnectionImpl.Config.class)
+public class OidcConnectionImpl implements OidcConnection{
+
+ @ObjectClassDefinition(name = "OpenID Connect connection details")
+ public @interface Config {
+ String name();
+ String baseUrl();
+ String clientId();
+ @AttributeDefinition(type = AttributeType.PASSWORD) String clientSecret();
+ String[] scopes();
+ }
+
+ private Config cfg;
+
+ @Activate
+ public OidcConnectionImpl(Config cfg) {
+ this.cfg = cfg;
+ }
+
+ @Override
+ public String baseUrl() {
+ return cfg.baseUrl();
+ }
+
+ @Override
+ public String clientId() {
+ return cfg.clientId();
+ }
+
+ @Override
+ public String clientSecret() {
+ return cfg.clientSecret();
+ }
+
+ @Override
+ public String[] scopes() {
+ return cfg.scopes();
+ }
+
+ @Override
+ public String name() {
+ return cfg.name();
+ }
+
+}
diff --git a/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/impl/OidcConnectionPersister.java b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/impl/OidcConnectionPersister.java
new file mode 100644
index 00000000..9693f393
--- /dev/null
+++ b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/impl/OidcConnectionPersister.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+package org.apache.sling.servlets.oidc_rp.impl;
+
+import org.apache.sling.api.resource.ResourceResolver;
+
+public interface OidcConnectionPersister {
+
+ void persistToken(ResourceResolver resourceResolver, String tokenValue);
+}
diff --git a/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/impl/OidcEntryPointServlet.java b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/impl/OidcEntryPointServlet.java
new file mode 100644
index 00000000..c8d92f53
--- /dev/null
+++ b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/impl/OidcEntryPointServlet.java
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+package org.apache.sling.servlets.oidc_rp.impl;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.http.HttpClient;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.servlets.SlingAllMethodsServlet;
+import org.apache.sling.servlets.annotations.SlingServletPaths;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+import com.nimbusds.oauth2.sdk.ResponseType;
+import com.nimbusds.oauth2.sdk.Scope;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.oauth2.sdk.id.State;
+import com.nimbusds.openid.connect.sdk.AuthenticationRequest;
+import com.nimbusds.openid.connect.sdk.Nonce;
+
+@Component(service = { Servlet.class })
+@SlingServletPaths(OidcEntryPointServlet.PATH)
+public class OidcEntryPointServlet extends SlingAllMethodsServlet {
+ private static final long serialVersionUID = 1L;
+
+ static final String SESSION_ATTRIBUTE_STATE = OidcEntryPointServlet.class.getName() + ".state";
+ static final String SESSION_ATTRIBUTE_REDIRECT = OidcEntryPointServlet.class.getName() + ".redirect";
+ static final String PATH = "/system/sling/oidc/entry-point"; // NOSONAR
+ private final OidcConnection connection;
+
+ @Activate
+ public OidcEntryPointServlet(@Reference OidcConnection connection) {
+ this.connection = connection;
+ }
+ @Override
+ protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
+ throws ServletException, IOException {
+
+ try {
+
+ Endpoints ep = Endpoints.discover(connection.baseUrl(), HttpClient.newHttpClient());
+
+ // The client ID provisioned by the OpenID provider when
+ // the client was registered
+ ClientID clientID = new ClientID(connection.clientId());
+
+ // The client callback URL
+ URI callback = new URI(OidcCallbackServlet.getCallbackUri(request));
+ // Generate random state string to securely pair the callback to this request
+ State state = new State();
+ request.getSession().setAttribute(SESSION_ATTRIBUTE_STATE, state);
+ if ( request.getParameter("redirect") != null )
+ request.getSession().setAttribute(SESSION_ATTRIBUTE_REDIRECT, request.getParameter("redirect"));
+
+ // Generate nonce for the ID token
+ Nonce nonce = new Nonce();
+
+ // Compose the OpenID authentication request (for the code flow)
+ AuthenticationRequest authRequest = new AuthenticationRequest.Builder(new ResponseType("code"), new Scope(connection.scopes()),
+ clientID, callback).endpointURI(new URI(ep.authorizationEndpoint())).state(state).nonce(nonce).build();
+
+ response.sendRedirect(authRequest.toURI().toString());
+ } catch (URISyntaxException e) {
+ throw new ServletException(e);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+}
diff --git a/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/package-info.java b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/package-info.java
new file mode 100644
index 00000000..632d6dfd
--- /dev/null
+++ b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+@Version("0.1.0")
+package org.apache.sling.servlets.oidc_rp;
+
+import org.osgi.annotation.versioning.Version;