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 2024/09/24 13:23:49 UTC
(sling-samples) branch master updated: feat(oauth): add a GitHub demo page
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-samples.git
The following commit(s) were added to refs/heads/master by this push:
new baa6f52 feat(oauth): add a GitHub demo page
baa6f52 is described below
commit baa6f521eb9ad4c4e43abc9d9619a2653754d9c7
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Tue Sep 24 15:23:10 2024 +0200
feat(oauth): add a GitHub demo page
---
.../oauth_demo/GithubRepositoriesModel.java | 72 ++++++++++++++++++++++
.../GithubRepositoriesSearchResponse.java | 26 ++++++++
.../sling/samples/oauth_demo/GithubRepository.java | 25 ++++++++
.../samples/oauth_demo/impl/GithubHttpClient.java | 57 +++++++++++++++++
...SearchServlet.java => GithubSearchServlet.java} | 59 +++++++++---------
.../oauth_demo/impl/YoutubeSearchServlet.java | 4 +-
oauth/launcher/Makefile | 9 +++
oauth/launcher/src/main/features/launcher.json | 8 +++
.../components/github-repos/.content.xml | 22 +++++++
.../components/github-repos/github-repos.html | 36 +++++++++++
.../oauth-demo/components/welcome/welcome.html | 1 +
.../content/oauth-demo/repositories/.content.xml | 24 ++++++++
12 files changed, 311 insertions(+), 32 deletions(-)
diff --git a/oauth/core/src/main/java/org/apache/sling/samples/oauth_demo/GithubRepositoriesModel.java b/oauth/core/src/main/java/org/apache/sling/samples/oauth_demo/GithubRepositoriesModel.java
new file mode 100644
index 0000000..28e0f0d
--- /dev/null
+++ b/oauth/core/src/main/java/org/apache/sling/samples/oauth_demo/GithubRepositoriesModel.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.samples.oauth_demo;
+
+import java.io.IOException;
+import java.net.URI;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.extensions.oauth_client.ClientConnection;
+import org.apache.sling.extensions.oauth_client.OAuthToken;
+import org.apache.sling.extensions.oauth_client.OAuthTokenStore;
+import org.apache.sling.extensions.oauth_client.OAuthUris;
+import org.apache.sling.extensions.oauth_client.TokenState;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.annotations.injectorspecific.OSGiService;
+import org.apache.sling.models.annotations.injectorspecific.SlingObject;
+import org.apache.sling.samples.oauth_demo.impl.GithubHttpClient;
+
+@Model(adaptables = SlingHttpServletRequest.class)
+public class GithubRepositoriesModel {
+
+ @SlingObject
+ private SlingHttpServletRequest request;
+
+ // get a reference to an OSGi service
+ @OSGiService(filter = "(name=github)")
+ private ClientConnection connection;
+
+ @OSGiService
+ private OAuthTokenStore tokenStore;
+
+ public boolean needsLogin() {
+ return getAccessToken().getState() != TokenState.VALID;
+ }
+
+ public boolean hasSearch() {
+ String searchParam = request.getParameter("search");
+
+ return searchParam != null && searchParam.trim().length() > 0;
+ }
+
+ private OAuthToken getAccessToken() {
+ return tokenStore.getAccessToken(connection, request.getResourceResolver());
+ }
+
+ public GithubRepositoriesSearchResponse getSearchResponse() throws IOException, InterruptedException {
+ if ( hasSearch() ) {
+ return new GithubHttpClient().searchRepositories(request.getParameter("search"), getAccessToken().getValue());
+ }
+
+ return null;
+ }
+
+ public URI getGithubLoginUrl() {
+ return OAuthUris.getOidcEntryPointUri(connection, request, request.getRequestURI());
+ }
+
+}
diff --git a/oauth/core/src/main/java/org/apache/sling/samples/oauth_demo/GithubRepositoriesSearchResponse.java b/oauth/core/src/main/java/org/apache/sling/samples/oauth_demo/GithubRepositoriesSearchResponse.java
new file mode 100644
index 0000000..69d91b7
--- /dev/null
+++ b/oauth/core/src/main/java/org/apache/sling/samples/oauth_demo/GithubRepositoriesSearchResponse.java
@@ -0,0 +1,26 @@
+/*
+ * 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.samples.oauth_demo;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public record GithubRepositoriesSearchResponse(@JsonProperty("total_count") int totalCount,
+ List<GithubRepository> items) {
+
+}
diff --git a/oauth/core/src/main/java/org/apache/sling/samples/oauth_demo/GithubRepository.java b/oauth/core/src/main/java/org/apache/sling/samples/oauth_demo/GithubRepository.java
new file mode 100644
index 0000000..31bfd61
--- /dev/null
+++ b/oauth/core/src/main/java/org/apache/sling/samples/oauth_demo/GithubRepository.java
@@ -0,0 +1,25 @@
+/*
+ * 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.samples.oauth_demo;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public record GithubRepository(String name,
+ @JsonProperty("full_name") String fullName,
+ @JsonProperty("html_url") String htmlUrl,
+ String description) {
+}
diff --git a/oauth/core/src/main/java/org/apache/sling/samples/oauth_demo/impl/GithubHttpClient.java b/oauth/core/src/main/java/org/apache/sling/samples/oauth_demo/impl/GithubHttpClient.java
new file mode 100644
index 0000000..a81cbe3
--- /dev/null
+++ b/oauth/core/src/main/java/org/apache/sling/samples/oauth_demo/impl/GithubHttpClient.java
@@ -0,0 +1,57 @@
+/*
+ * 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.samples.oauth_demo.impl;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URLEncoder;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.sling.samples.oauth_demo.GithubRepositoriesSearchResponse;
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class GithubHttpClient {
+ public static final String API_BASE = "https://api.github.com";
+
+ public GithubRepositoriesSearchResponse searchRepositories(String query, String accessToken) throws IOException, InterruptedException {
+
+ try ( HttpClient client = HttpClient.newHttpClient() ) {
+ query = URLEncoder.encode(query.trim(), StandardCharsets.UTF_8);
+ HttpRequest searchRequest = HttpRequest.newBuilder()
+ .uri(URI.create(API_BASE + "/search/repositories?q=" + query))
+ .header("Authorization", "Bearer " + accessToken)
+ .header("Accept", "application/vnd.github+json")
+ .header("X-GitHub-Api-Version", "2022-11-28")
+ .build();
+
+ HttpResponse<String> response = client.send(searchRequest, BodyHandlers.ofString());
+
+ String body = response.body();
+
+ ObjectMapper mapper = new ObjectMapper();
+ // client mapping is still incomplete
+ mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ return mapper.reader().readValue(body, GithubRepositoriesSearchResponse.class);
+ }
+ }
+}
diff --git a/oauth/core/src/main/java/org/apache/sling/samples/oauth_demo/impl/YoutubeSearchServlet.java b/oauth/core/src/main/java/org/apache/sling/samples/oauth_demo/impl/GithubSearchServlet.java
similarity index 52%
copy from oauth/core/src/main/java/org/apache/sling/samples/oauth_demo/impl/YoutubeSearchServlet.java
copy to oauth/core/src/main/java/org/apache/sling/samples/oauth_demo/impl/GithubSearchServlet.java
index 363e251..202b230 100644
--- a/oauth/core/src/main/java/org/apache/sling/samples/oauth_demo/impl/YoutubeSearchServlet.java
+++ b/oauth/core/src/main/java/org/apache/sling/samples/oauth_demo/impl/GithubSearchServlet.java
@@ -24,55 +24,54 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
-import org.apache.sling.api.request.RequestDispatcherOptions;
+import org.apache.sling.extensions.oauth_client.ClientConnection;
import org.apache.sling.extensions.oauth_client.OAuthToken;
import org.apache.sling.extensions.oauth_client.OAuthTokenRefresher;
import org.apache.sling.extensions.oauth_client.OAuthTokenStore;
-import org.apache.sling.extensions.oauth_client.OidcConnection;
import org.apache.sling.extensions.oauth_client.support.OAuthEnabledSlingServlet;
-import org.apache.sling.samples.oauth_demo.YoutubeSearchResponse;
-import org.apache.sling.servlets.annotations.SlingServletResourceTypes;
+import org.apache.sling.samples.oauth_demo.GithubRepositoriesSearchResponse;
+import org.apache.sling.servlets.annotations.SlingServletPaths;
import org.jetbrains.annotations.NotNull;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
@Component(service = { Servlet.class })
-@SlingServletResourceTypes(resourceTypes = "oauth-demo/components/youtube-videos", methods = "GET", extensions = "html")
-public class YoutubeSearchServlet extends OAuthEnabledSlingServlet {
+@SlingServletPaths("/bin/github/search")
+public class GithubSearchServlet extends OAuthEnabledSlingServlet {
- private static final long serialVersionUID = 1L;
-
@Activate
- public YoutubeSearchServlet(@Reference OidcConnection connection, @Reference OAuthTokenStore tokenStore, @Reference OAuthTokenRefresher oidcClient) {
+ public GithubSearchServlet(@Reference(target = "(name=github)") ClientConnection connection,
+ @Reference OAuthTokenStore tokenStore, @Reference OAuthTokenRefresher oidcClient) {
super(connection, tokenStore, oidcClient);
}
- @Override
- protected @NotNull String getRedirectPath(@NotNull SlingHttpServletRequest request) {
- return request.getRequestURI();
- }
-
@Override
protected void doGetWithToken(@NotNull SlingHttpServletRequest request, @NotNull SlingHttpServletResponse response,
- OAuthToken token) throws IOException, ServletException {
+ OAuthToken token) throws ServletException, IOException {
- try {
- String search = request.getParameter("search");
- if (search != null && !search.isBlank()) {
- YoutubeHttpClient client = new YoutubeHttpClient();
- YoutubeSearchResponse youtubeResponse = client.searchVideos(search, token.getValue());
- request.setAttribute("ytResponse", youtubeResponse);
+ String search = request.getParameter("search");
+
+ if (search != null && !search.isBlank()) {
+ try {
+ GithubHttpClient client = new GithubHttpClient();
+ GithubRepositoriesSearchResponse repositories = client.searchRepositories(search, token.getValue());
+
+ response.setContentType("text/plain");
+ response.getWriter().write("Repositories found: " + repositories.totalCount() + "\n\n");
+ repositories.items().forEach(repo -> {
+ try {
+ response.getWriter().write(repo.fullName() + "\n");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Please retry later");
}
-
- RequestDispatcherOptions opts = new RequestDispatcherOptions();
- opts.setReplaceSelectors("show");
-
- response.setContentType("text/html");
- request.getRequestDispatcher(request.getResource(), opts).include(request, response);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Please retry later");
}
+
}
+
}
diff --git a/oauth/core/src/main/java/org/apache/sling/samples/oauth_demo/impl/YoutubeSearchServlet.java b/oauth/core/src/main/java/org/apache/sling/samples/oauth_demo/impl/YoutubeSearchServlet.java
index 363e251..4f9e218 100644
--- a/oauth/core/src/main/java/org/apache/sling/samples/oauth_demo/impl/YoutubeSearchServlet.java
+++ b/oauth/core/src/main/java/org/apache/sling/samples/oauth_demo/impl/YoutubeSearchServlet.java
@@ -25,10 +25,10 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.request.RequestDispatcherOptions;
+import org.apache.sling.extensions.oauth_client.ClientConnection;
import org.apache.sling.extensions.oauth_client.OAuthToken;
import org.apache.sling.extensions.oauth_client.OAuthTokenRefresher;
import org.apache.sling.extensions.oauth_client.OAuthTokenStore;
-import org.apache.sling.extensions.oauth_client.OidcConnection;
import org.apache.sling.extensions.oauth_client.support.OAuthEnabledSlingServlet;
import org.apache.sling.samples.oauth_demo.YoutubeSearchResponse;
import org.apache.sling.servlets.annotations.SlingServletResourceTypes;
@@ -44,7 +44,7 @@ public class YoutubeSearchServlet extends OAuthEnabledSlingServlet {
private static final long serialVersionUID = 1L;
@Activate
- public YoutubeSearchServlet(@Reference OidcConnection connection, @Reference OAuthTokenStore tokenStore, @Reference OAuthTokenRefresher oidcClient) {
+ public YoutubeSearchServlet(@Reference(target = "(name=google)") ClientConnection connection, @Reference OAuthTokenStore tokenStore, @Reference OAuthTokenRefresher oidcClient) {
super(connection, tokenStore, oidcClient);
}
diff --git a/oauth/launcher/Makefile b/oauth/launcher/Makefile
index dc1e5d2..5a11b0f 100644
--- a/oauth/launcher/Makefile
+++ b/oauth/launcher/Makefile
@@ -13,12 +13,21 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+DEBUG_FLAGS := -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005
+export JAVA_OPTS := $(DEBUG_FLAGS)
+
secrets/google/clientId:
@echo [WARN] $@ missing, OIDC config needs to be created manually
secrets/google/clientSecret:
@echo [WARN] $@ missing, OIDC config needs to be created manually
+secrets/github/clientId:
+ @echo [WARN] $@ missing, OAuth config needs to be created manually
+
+secrets/github/clientSecret:
+ @echo [WARN] $@ missing, OAuth config needs to be created manually
+
run: target/dependency/org.apache.sling.feature.launcher/bin/launcher secrets/google/clientId secrets/google/clientSecret
@echo '-------------------------------------------------------------------------------------------'
@echo '[NOTE] Launching application, this will fail if you did not build the project at least once'
diff --git a/oauth/launcher/src/main/features/launcher.json b/oauth/launcher/src/main/features/launcher.json
index 6fd3196..11e450a 100644
--- a/oauth/launcher/src/main/features/launcher.json
+++ b/oauth/launcher/src/main/features/launcher.json
@@ -22,6 +22,14 @@
"clientSecret": "$[secret:google/clientSecret]",
"scopes": ["openid", "https://www.googleapis.com/auth/youtube.force-ssl"]
},
+ "org.apache.sling.extensions.oauth_client.impl.OAuthConnectionImpl~github": {
+ "name": "github",
+ "authorizationEndpoint": "https://github.com/login/oauth/authorize",
+ "tokenEndpoint": "https://github.com/login/oauth/access_token",
+ "clientId": "$[secret:github/clientId]",
+ "clientSecret": "$[secret:github/clientSecret]",
+ "scopes": ["user:email"]
+ },
"org.apache.sling.extensions.oauth_client.impl.JcrUserHomeOAuthTokenStore" : {
}
}
diff --git a/oauth/ui.apps/src/main/content/jcr_root/apps/oauth-demo/components/github-repos/.content.xml b/oauth/ui.apps/src/main/content/jcr_root/apps/oauth-demo/components/github-repos/.content.xml
new file mode 100644
index 0000000..20e138a
--- /dev/null
+++ b/oauth/ui.apps/src/main/content/jcr_root/apps/oauth-demo/components/github-repos/.content.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
+ jcr:primaryType="sling:Folder"
+/>
diff --git a/oauth/ui.apps/src/main/content/jcr_root/apps/oauth-demo/components/github-repos/github-repos.html b/oauth/ui.apps/src/main/content/jcr_root/apps/oauth-demo/components/github-repos/github-repos.html
new file mode 100644
index 0000000..f9ebca5
--- /dev/null
+++ b/oauth/ui.apps/src/main/content/jcr_root/apps/oauth-demo/components/github-repos/github-repos.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <link href="/oauth-demo/static/css/output.css" rel="stylesheet">
+</head>
+<body data-sly-use.github="org.apache.sling.samples.oauth_demo.GithubRepositoriesModel">
+ <main>
+ <header class="flex flex-row justify-between">
+ <h1>GitHub Repositories</h1>
+ <form>
+ <input type="text" id="search" name="search" value="${youtube.search}">
+ <button type="submit" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">Search</button>
+ </form>
+ </header>
+ <div data-sly-test="${github.searchResponse}">
+ <p>Repository count: ${github.searchResponse.totalCount}</p>
+ <div data-sly-list.item="${github.searchResponse.items}"
+ class="flex flex-row flex-wrap">
+ <div class="basis-1/3">
+ <div class="px-4 bg-white rounded overflow-hidden shadow-lg m-2">
+ <a href="${item.htmlUrl}" target="_blank">
+ <h2>${item.fullName}</h2>
+ <p>${item.description}</p>
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div data-sly-test="${github.needsLogin}">
+ <p>Please <a href="${github.githubLoginUrl}">connect your account to github</a> to view the repositories.</p>
+ </div>
+ </main>
+</body>
+</html>
\ No newline at end of file
diff --git a/oauth/ui.apps/src/main/content/jcr_root/apps/oauth-demo/components/welcome/welcome.html b/oauth/ui.apps/src/main/content/jcr_root/apps/oauth-demo/components/welcome/welcome.html
index d424700..df5f709 100644
--- a/oauth/ui.apps/src/main/content/jcr_root/apps/oauth-demo/components/welcome/welcome.html
+++ b/oauth/ui.apps/src/main/content/jcr_root/apps/oauth-demo/components/welcome/welcome.html
@@ -9,6 +9,7 @@
<main>
<h1>Sling OAuth Demo</h1>
<p>Access the <a class="underline" href="oauth-demo/videos.html">YouTube videos</a> page.</p>
+ <p>Access the <a class="underline" href="oauth-demo/repositories.html">GitHub repositories</a> page.</p>
</main>
</body>
</html>
\ No newline at end of file
diff --git a/oauth/ui.apps/src/main/content/jcr_root/content/oauth-demo/repositories/.content.xml b/oauth/ui.apps/src/main/content/jcr_root/content/oauth-demo/repositories/.content.xml
new file mode 100644
index 0000000..4ca959b
--- /dev/null
+++ b/oauth/ui.apps/src/main/content/jcr_root/content/oauth-demo/repositories/.content.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
+ xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
+ jcr:primaryType="sling:Folder"
+ sling:resourceType="oauth-demo/components/github-repos"
+/>
\ No newline at end of file