diff --git a/.gitignore b/.gitignore
index ee75b2ffc..c2bd7acae 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,8 @@
#ignore patches
**/patches
+
+# ignore enviroment related files
+*.swp
+/.vscode
+/.idea
+.DS_Store
diff --git a/Java-base/sling-org-apache-sling-auth-core/Dockerfile b/Java-base/sling-org-apache-sling-auth-core/Dockerfile
new file mode 100644
index 000000000..e208c4890
--- /dev/null
+++ b/Java-base/sling-org-apache-sling-auth-core/Dockerfile
@@ -0,0 +1,28 @@
+FROM ubuntu:22.04
+
+RUN export DEBIAN_FRONTEND=noninteractive \
+ && apt-get update \
+ && apt-get install -y software-properties-common \
+ && add-apt-repository ppa:deadsnakes/ppa \
+ && apt-get update \
+ && apt-get install -y \
+ build-essential \
+ git \
+ vim \
+ jq \
+ && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/list/*
+
+RUN apt-get -y install sudo \
+ openjdk-8-jdk \
+ maven
+
+RUN bash -c "echo 2 | update-alternatives --config java"
+
+COPY src /workspace
+WORKDIR /workspace
+
+RUN mvn install -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false
+
+RUN mvn test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100
+
+ENV TZ=Asia/Seoul
diff --git a/Java-base/sling-org-apache-sling-auth-core/src/CODE_OF_CONDUCT.md b/Java-base/sling-org-apache-sling-auth-core/src/CODE_OF_CONDUCT.md
new file mode 100644
index 000000000..0fa18e593
--- /dev/null
+++ b/Java-base/sling-org-apache-sling-auth-core/src/CODE_OF_CONDUCT.md
@@ -0,0 +1,22 @@
+
+Apache Software Foundation Code of Conduct
+====
+
+Being an Apache project, Apache Sling adheres to the Apache Software Foundation's [Code of Conduct](https://www.apache.org/foundation/policies/conduct.html).
diff --git a/Java-base/sling-org-apache-sling-auth-core/src/CONTRIBUTING.md b/Java-base/sling-org-apache-sling-auth-core/src/CONTRIBUTING.md
new file mode 100644
index 000000000..ac82a1abe
--- /dev/null
+++ b/Java-base/sling-org-apache-sling-auth-core/src/CONTRIBUTING.md
@@ -0,0 +1,24 @@
+
+Contributing
+====
+
+Thanks for choosing to contribute!
+
+You will find all the necessary details about how you can do this at https://sling.apache.org/contributing.html.
diff --git a/Java-base/sling-org-apache-sling-auth-core/src/Jenkinsfile b/Java-base/sling-org-apache-sling-auth-core/src/Jenkinsfile
new file mode 100644
index 000000000..f5825190c
--- /dev/null
+++ b/Java-base/sling-org-apache-sling-auth-core/src/Jenkinsfile
@@ -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.
+ */
+
+slingOsgiBundleBuild()
diff --git a/Java-base/sling-org-apache-sling-auth-core/src/LICENSE b/Java-base/sling-org-apache-sling-auth-core/src/LICENSE
new file mode 100644
index 000000000..d64569567
--- /dev/null
+++ b/Java-base/sling-org-apache-sling-auth-core/src/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/Java-base/sling-org-apache-sling-auth-core/src/README.md b/Java-base/sling-org-apache-sling-auth-core/src/README.md
new file mode 100644
index 000000000..0376abd97
--- /dev/null
+++ b/Java-base/sling-org-apache-sling-auth-core/src/README.md
@@ -0,0 +1,9 @@
+[](https://sling.apache.org)
+
+ [![Build Status](https://builds.apache.org/buildStatus/icon?job=Sling/sling-org-apache-sling-auth-core/master)](https://builds.apache.org/job/Sling/job/sling-org-apache-sling-auth-core/job/master) [![Test Status](https://img.shields.io/jenkins/t/https/builds.apache.org/job/Sling/job/sling-org-apache-sling-auth-core/job/master.svg)](https://builds.apache.org/job/Sling/job/sling-org-apache-sling-auth-core/job/master/test_results_analyzer/) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.apache.sling/org.apache.sling.auth.core/badge.svg)](https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.apache.sling%22%20a%3A%22org.apache.sling.auth.core%22) [![JavaDocs](https://www.javadoc.io/badge/org.apache.sling/org.apache.sling.auth.core.svg)](https://www.javadoc.io/doc/org.apache.sling/org.apache.sling.auth.core) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0) [![auth](https://sling.apache.org/badges/group-auth.svg)](https://github.com/apache/sling-aggregator/blob/master/docs/groups/auth.md)
+
+# Apache Sling Authentication Service
+
+This module is part of the [Apache Sling](https://sling.apache.org) project.
+
+The Sling Authentication Service bundle provides the basic mechanisms to authenticate HTTP requests with a JCR repository. The algorithms for extracting authentication details from the requests is extensible by implementing an AuthenticationHandler interface.
diff --git a/Java-base/sling-org-apache-sling-auth-core/src/bnd.bnd b/Java-base/sling-org-apache-sling-auth-core/src/bnd.bnd
new file mode 100644
index 000000000..c910ed73a
--- /dev/null
+++ b/Java-base/sling-org-apache-sling-auth-core/src/bnd.bnd
@@ -0,0 +1,3 @@
+Import-Package: !javax.jcr,*
+DynamicImport-Package: javax.jcr
+Bundle-DocURL: http://sling.apache.org/site/authentication.html
\ No newline at end of file
diff --git a/Java-base/sling-org-apache-sling-auth-core/src/pom.xml b/Java-base/sling-org-apache-sling-auth-core/src/pom.xml
new file mode 100644
index 000000000..5dd5b1bd6
--- /dev/null
+++ b/Java-base/sling-org-apache-sling-auth-core/src/pom.xml
@@ -0,0 +1,150 @@
+
+
+
+
+ 4.0.0
+
+ org.apache.sling
+ sling-bundle-parent
+ 38
+
+
+
+ org.apache.sling.auth.core
+ 1.4.9-SNAPSHOT
+
+ Apache Sling Auth Core
+
+ The Sling Authentication Service bundle provides the basic
+ mechanisms to authenticate HTTP requests with a JCR repository.
+ The algorithms for extracting authentication details from the
+ requests is extensible by implementing an AuthenticationHandler
+ interface.
+
+
+
+ 12315268
+ **.impl.**
+
+
+
+ scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-auth-core.git
+ scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-auth-core.git
+ https://gitbox.apache.org/repos/asf?p=sling-org-apache-sling-auth-core.git
+ HEAD
+
+
+
+
+ org.osgi
+ org.osgi.annotation.versioning
+
+
+ org.osgi
+ org.osgi.service.component.annotations
+
+
+ org.osgi
+ org.osgi.service.metatype.annotations
+
+
+ org.osgi
+ org.osgi.service.component
+
+
+ org.apache.sling
+ org.apache.sling.api
+ 2.18.0
+ provided
+
+
+ org.apache.sling
+ org.apache.sling.commons.osgi
+ 2.2.0
+ provided
+
+
+ javax.jcr
+ jcr
+
+
+ javax.servlet
+ javax.servlet-api
+
+
+ org.osgi
+ osgi.core
+
+
+ org.osgi
+ org.osgi.service.event
+ 1.3.1
+ provided
+
+
+ org.osgi
+ org.osgi.service.http.whiteboard
+ 1.1.0
+ provided
+
+
+ org.slf4j
+ slf4j-api
+
+
+ org.jmock
+ jmock-junit4
+
+
+ commons-codec
+ commons-codec
+ 1.4
+ provided
+
+
+
+
+ junit
+ junit
+
+
+ org.mockito
+ mockito-core
+ 3.3.3
+ test
+
+
+ org.slf4j
+ slf4j-simple
+
+
+ junit-addons
+ junit-addons
+ 1.4
+ test
+
+
+ com.google.guava
+ guava
+ 15.0
+ test
+
+
+
diff --git a/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/AuthConstants.java b/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/AuthConstants.java
new file mode 100644
index 000000000..b0c073c89
--- /dev/null
+++ b/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/AuthConstants.java
@@ -0,0 +1,151 @@
+/*
+ * 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.auth.core;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * The AuthConstants provides a collection of constants used to
+ * configure and customize the Sling authentication infrastructure.
+ *
+ * This class can neither be extended from nor can it be instantiated.
+ *
+ * @since 1.1 (bundle version 1.0.8)
+ */
+public final class AuthConstants {
+
+ /**
+ * The name of the request parameter indicating that the submitted username
+ * and password should just be checked and a status code be set for success
+ * (200/OK) or failure (403/FORBIDDEN).
+ *
+ * @see AuthUtil#isValidateRequest(HttpServletRequest)
+ * @see AuthUtil#sendValid(HttpServletResponse)
+ * @see AuthUtil#sendInvalid(HttpServletRequest, HttpServletResponse)
+ */
+ public static final String PAR_J_VALIDATE = "j_validate";
+
+ /**
+ * The name of the request header set by the
+ * {@link AuthUtil#sendInvalid(HttpServletRequest, HttpServletResponse)} method if the provided
+ * credentials cannot be used for login.
+ *
+ * This header may be inspected by clients for a reason why the request
+ * failed.
+ *
+ * @see AuthUtil#sendInvalid(HttpServletRequest, HttpServletResponse)
+ */
+ public static final String X_REASON = "X-Reason";
+
+ /**
+ * The name of the request header set by the
+ * {@link AuthUtil#sendInvalid(HttpServletRequest, HttpServletResponse)} method if the provided
+ * credentials cannot be used for login.
+ *
+ * This header may be inspected by clients for a a detailed reason code why the request
+ * failed.
+ *
+ * @see AuthUtil#sendInvalid(HttpServletRequest, HttpServletResponse)
+ */
+ public static final String X_REASON_CODE = "X-Reason-Code";
+
+ /**
+ * Service Registration property which may be set by an
+ * {@link org.apache.sling.auth.core.spi.AuthenticationHandler} service to
+ * indicate whether its
+ * {@link org.apache.sling.auth.core.spi.AuthenticationHandler#requestCredentials(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}
+ * method supports non-browser requests (according to
+ * {@link AuthUtil#isBrowserRequest(javax.servlet.http.HttpServletRequest)}
+ * or not.
+ *
+ * For backwards compatibility with existing
+ * {@link org.apache.sling.auth.core.spi.AuthenticationHandler} services the
+ * default assumption in the absence of this property is that all requests
+ * are supported.
+ *
+ * If this property is set to true or yes
+ * (case-insensitive check) the handler is not called for requests assumed
+ * to be sent from non-browser clients. Any other value of this property
+ * indicates support for non-browser requests by the handler.
+ *
+ * Note that this property only influences whether the
+ * requestCredentials method is called or not. The
+ * extractCredentials and dropCredentials are
+ * called regardless of this property.
+ */
+ public static final String AUTH_HANDLER_BROWSER_ONLY = "sling.auth.browser-only";
+
+ /**
+ * Marker property in the
+ * {@link org.apache.sling.auth.core.spi.AuthenticationInfo} object returned
+ * by the
+ * {@link org.apache.sling.auth.core.spi.AuthenticationHandler#extractCredentials(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}
+ * method indicating a first authentication considered to be a login.
+ *
+ * By setting this property to any non-null value an
+ * {@link org.apache.sling.auth.core.spi.AuthenticationHandler} indicates,
+ * that the {@link #TOPIC_LOGIN} event should be fired after successfully
+ * acquiring the ResourceResolver.
+ */
+ public static final String AUTH_INFO_LOGIN = "$$auth.info.login$$";
+
+ /**
+ * The topic for the OSGi event which is sent when a user has logged in successfully.
+ * The event contains at least the {@link org.apache.sling.api.SlingConstants#PROPERTY_USERID}
+ * and the {@link org.apache.sling.auth.core.spi.AuthenticationInfo#AUTH_TYPE}
+ * properties.
+ */
+ public static final String TOPIC_LOGIN = "org/apache/sling/auth/core/Authenticator/LOGIN";
+
+ /**
+ * The topic for the OSGi event which is sent when a user has failed to login successfully.
+ * The event contains at least the {@link org.apache.sling.api.SlingConstants#PROPERTY_USERID}
+ * and the {@link org.apache.sling.auth.core.spi.AuthenticationInfo#AUTH_TYPE}
+ * properties.
+ */
+ public static final String TOPIC_LOGIN_FAILED = "org/apache/sling/auth/core/Authenticator/LOGIN_FAILED";
+
+ /**
+ * Any OSGi service may provide a {@code sling.auth.requirements} registration property which is used
+ * to dynamically extend the authentication requirements for the {@code AuthenticationSupport}.
+ * This may for example be set by AuthenticationHandler implementations providing
+ * a login form to ensure access to the login form does not require authentication. The value
+ * of this property is a single string, an array of strings or a Collection of strings.
+ * Each string can be an absolute path (such as /content) or and absolute URI (such as
+ * http://thehost/content). Optionally each entry may be prefixed by a plus (+) or minus (-) sign
+ * indicating that authentication is required (plus) or not required (minus).
+ */
+ public static final String AUTH_REQUIREMENTS = "sling.auth.requirements";
+
+ /**
+ * The name of the request attribute containing the list of
+ * request URI suffixes handled by the default authenticator
+ * org.apache.sling.auth.core.impl.SlingAuthenticator.
+ * The authenticator will populate this attribute so that login JSPs
+ * can post j_username and j_password to the correct URI.
+ *
+ * @since 1.3.2 (bundle version 1.4.0)
+ */
+ public static final String ATTR_REQUEST_AUTH_URI_SUFFIX = "org.apache.sling.api.include.auth_uri_suffix";
+
+ private AuthConstants() {
+ }
+
+}
diff --git a/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/AuthUtil.java b/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/AuthUtil.java
new file mode 100644
index 000000000..771b8f83f
--- /dev/null
+++ b/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/AuthUtil.java
@@ -0,0 +1,603 @@
+/*
+ * 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.auth.core;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.regex.Pattern;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.api.auth.Authenticator;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.auth.core.spi.AuthenticationHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The AuthUtil provides utility functions for implementations of
+ * {@link org.apache.sling.auth.core.spi.AuthenticationHandler} services and
+ * users of the Sling authentication infrastructure.
+ *
+ * This utility class can neither be extended from nor can it be instantiated.
+ *
+ * @since 1.1 (bundle version 1.0.8)
+ */
+public final class AuthUtil {
+
+ /**
+ * Request header commonly set by Ajax Frameworks to indicate the request is
+ * posted as an Ajax request. The value set is expected to be
+ * {@link #XML_HTTP_REQUEST}.
+ *
+ * This header is known to be set by JQuery, ExtJS and Prototype. Other
+ * client-side JavaScript framework most probably also set it.
+ *
+ * @see #isAjaxRequest(javax.servlet.http.HttpServletRequest)
+ */
+ private static final String X_REQUESTED_WITH = "X-Requested-With";
+
+ /**
+ * The expected value of the {@link #X_REQUESTED_WITH} request header to
+ * identify a request as an Ajax request.
+ *
+ * @see #isAjaxRequest(javax.servlet.http.HttpServletRequest)
+ */
+ private static final String XML_HTTP_REQUEST = "XMLHttpRequest";
+
+ /**
+ * Request header providing the clients user agent information used
+ * by {@link #isBrowserRequest(HttpServletRequest)} to decide whether
+ * a request is probably sent by a browser or not.
+ */
+ private static final String USER_AGENT = "User-Agent";
+
+ /**
+ * String contained in a {@link #USER_AGENT} header indicating a Mozilla
+ * class browser. Examples of such browsers are Firefox (generally Gecko
+ * based browsers), Safari, Chrome (probably generally WebKit based
+ * browsers), and Microsoft IE.
+ */
+ private static final String BROWSER_CLASS_MOZILLA = "Mozilla";
+
+ /**
+ * String contained in a {@link #USER_AGENT} header indicating a Opera class
+ * browser. The only known browser in this class is the Opera browser.
+ */
+ private static final String BROWSER_CLASS_OPERA = "Opera";
+
+ // no instantiation
+ private AuthUtil() {
+ }
+
+ /**
+ * Returns the value of the named request attribute or parameter as a string
+ * as follows:
+ *
+ *
If there is a request attribute of that name, which is a non-empty
+ * string, it is returned.
+ *
If there is a non-empty request parameter of
+ * that name, this parameter is returned.
+ *
Otherwise the defaultValue is returned.
+ *
+ *
+ * @param request The request from which to return the attribute or request
+ * parameter
+ * @param name The name of the attribute/parameter
+ * @param defaultValue The default value to use if neither a non-empty
+ * string attribute or a non-empty parameter exists in the
+ * request.
+ * @return The attribute, parameter or defaultValue as defined
+ * above.
+ */
+ public static String getAttributeOrParameter(
+ final HttpServletRequest request, final String name,
+ final String defaultValue) {
+
+ final String resourceAttr = getAttributeString(request, name);
+ if (resourceAttr != null) {
+ return resourceAttr;
+ }
+
+ final String resource = request.getParameter(name);
+ if (resource != null && resource.length() > 0) {
+ return resource;
+ }
+
+ return defaultValue;
+ }
+
+ /**
+ * Returns any resource target to redirect to after successful
+ * authentication. This method either returns a non-empty string or the
+ * defaultLoginResource parameter. First the
+ * resource request attribute is checked. If it is a non-empty
+ * string, it is returned. Second the resource request
+ * parameter is checked and returned if it is a non-empty string.
+ *
+ * @param request The request providing the attribute or parameter
+ * @param defaultLoginResource The default login resource value
+ * @return The non-empty redirection target or
+ * defaultLoginResource.
+ */
+ public static String getLoginResource(final HttpServletRequest request,
+ String defaultLoginResource) {
+ return getAttributeOrParameter(request, Authenticator.LOGIN_RESOURCE,
+ defaultLoginResource);
+ }
+
+ /**
+ * Ensures and returns the {@link Authenticator#LOGIN_RESOURCE} request
+ * attribute is set to a non-null, non-empty string. If the attribute is not
+ * currently set, this method sets it as follows:
+ *
+ *
If the {@link Authenticator#LOGIN_RESOURCE} request parameter is set
+ * to a non-empty string, that parameter is set
+ *
Otherwise if the defaultValue is a non-empty string the
+ * default value is used
+ *
Otherwise the attribute is set to "/"
+ *
+ *
+ * @param request The request to check for the resource attribute
+ * @param defaultValue The default value to use if the attribute is not set
+ * and the request parameter is not set. This parameter is
+ * ignored if it is null or an empty string.
+ * @return returns the value of resource request attribute
+ */
+ public static String setLoginResourceAttribute(
+ final HttpServletRequest request, final String defaultValue) {
+ String resourceAttr = getAttributeString(request,
+ Authenticator.LOGIN_RESOURCE);
+ if (resourceAttr == null) {
+ final String resourcePar = request.getParameter(Authenticator.LOGIN_RESOURCE);
+ if (resourcePar != null && resourcePar.length() > 0) {
+ resourceAttr = resourcePar;
+ } else if (defaultValue != null && defaultValue.length() > 0) {
+ resourceAttr = defaultValue;
+ } else {
+ resourceAttr = "/";
+ }
+ request.setAttribute(Authenticator.LOGIN_RESOURCE, resourceAttr);
+ }
+ return resourceAttr;
+ }
+
+ /**
+ * Redirects to the given target path appending any parameters provided in
+ * the parameter map.
+ *
+ * This method implements the following functionality:
+ *
+ *
If the params map does not contain a (non-
+ * null) value for the {@link Authenticator#LOGIN_RESOURCE
+ * resource} entry, such an entry is generated from the request URI and the
+ * (optional) query string of the given request.
+ *
The parameters from the params map or at least a single
+ * {@link Authenticator#LOGIN_RESOURCE resource} parameter are added to the
+ * target path for the redirect. Each parameter value is encoded using the
+ * java.net.URLEncoder with UTF-8 encoding to make it safe for
+ * requests
+ *
+ *
+ * After checking the redirect target and creating the target URL from the
+ * parameter map, the response buffer is reset and the
+ * HttpServletResponse.sendRedirect is called. Any headers
+ * already set before calling this method are preserved.
+ *
+ * @param request The request object used to get the current request URI and
+ * request query string if the params map does not
+ * have the {@link Authenticator#LOGIN_RESOURCE resource}
+ * parameter set.
+ * @param response The response used to send the redirect to the client.
+ * @param target The redirect target to validate. This path must be prefixed
+ * with the request's servlet context path. If this parameter is
+ * not a valid target request as per the
+ * {@link #isRedirectValid(HttpServletRequest, String)} method
+ * the target is modified to be the root of the request's
+ * context.
+ * @param params The map of parameters to be added to the target path. This
+ * may be null.
+ * @throws IOException If an error occurs sending the redirect request
+ * @throws IllegalStateException If the response was committed or if a
+ * partial URL is given and cannot be converted into a valid URL
+ * @throws InternalError If the UTF-8 character encoding is not supported by
+ * the platform. This should not be caught, because it is a real
+ * problem if the encoding required by the specification is
+ * missing.
+ */
+ public static void sendRedirect(final HttpServletRequest request,
+ final HttpServletResponse response, final String target,
+ Map params) throws IOException {
+
+ checkAndReset(response);
+
+ StringBuilder b = new StringBuilder();
+ if (AuthUtil.isRedirectValid(request, target)) {
+ b.append(target);
+ } else if (request.getContextPath().length() == 0) {
+ b.append("/");
+ } else {
+ b.append(request.getContextPath());
+ }
+
+ if (params == null) {
+ params = new HashMap();
+ }
+
+ // ensure the login resource is provided with the redirect
+ if (params.get(Authenticator.LOGIN_RESOURCE) == null) {
+ String resource = request.getRequestURI();
+ if (request.getQueryString() != null) {
+ resource += "?" + request.getQueryString();
+ }
+ params.put(Authenticator.LOGIN_RESOURCE, resource);
+ }
+
+ b.append('?');
+ Iterator> ei = params.entrySet().iterator();
+ while (ei.hasNext()) {
+ Entry entry = ei.next();
+ if (entry.getKey() != null && entry.getValue() != null) {
+ try {
+ b.append(entry.getKey()).append('=').append(
+ URLEncoder.encode(entry.getValue(), "UTF-8"));
+ } catch (UnsupportedEncodingException uee) {
+ throw new InternalError(
+ "Unexpected UnsupportedEncodingException for UTF-8");
+ }
+
+ if (ei.hasNext()) {
+ b.append('&');
+ }
+ }
+ }
+
+ response.sendRedirect(b.toString());
+ }
+
+ /**
+ * Returns the name request attribute if it is a non-empty string value.
+ *
+ * @param request The request from which to retrieve the attribute
+ * @param name The name of the attribute to return
+ * @return The named request attribute or null if the attribute
+ * is not set or is not a non-empty string value.
+ */
+ private static String getAttributeString(final HttpServletRequest request,
+ final String name) {
+ Object resObj = request.getAttribute(name);
+ if ((resObj instanceof String) && ((String) resObj).length() > 0) {
+ return (String) resObj;
+ }
+
+ // not set or not a non-empty string
+ return null;
+ }
+
+ /**
+ * Returns true if the the client just asks for validation of
+ * submitted username/password credentials.
+ *
+ * This implementation returns true if the request parameter
+ * {@link AuthConstants#PAR_J_VALIDATE} is set to true (case-insensitve). If
+ * the request parameter is not set or to any value other than
+ * true this method returns false.
+ *
+ * @param request The request to provide the parameter to check
+ * @return true if the {@link AuthConstants#PAR_J_VALIDATE} parameter is set
+ * to true.
+ */
+ public static boolean isValidateRequest(final HttpServletRequest request) {
+ return "true".equalsIgnoreCase(request.getParameter(AuthConstants.PAR_J_VALIDATE));
+ }
+
+ /**
+ * Sends a 200/OK response to a credential validation request.
+ *
+ * This method just overwrites the response status to 200/OK, sends no
+ * content (content length header set to zero) and prevents caching on
+ * clients and proxies. Any other response headers set before calling this
+ * methods are preserved and sent along with the response.
+ *
+ * @param response The response object
+ * @throws IllegalStateException if the response has already been committed
+ */
+ public static void sendValid(final HttpServletResponse response) {
+ checkAndReset(response);
+ try {
+ response.setStatus(HttpServletResponse.SC_OK);
+
+ // explicitly tell we have no content but set content type
+ // to prevent firefox from trying to parse the response
+ // (SLING-1841)
+ response.setContentType("text/plain");
+ response.setContentLength(0);
+
+ // prevent the client from aggressively caching the response
+ // (SLING-1841)
+ response.setHeader("Pragma", "no-cache");
+ response.setHeader("Cache-Control", "no-cache");
+ response.addHeader("Cache-Control", "no-store");
+
+ response.flushBuffer();
+ } catch (IOException ioe) {
+ getLog().error("Failed to send 200/OK response", ioe);
+ }
+ }
+
+ /**
+ * Sends a 403/FORBIDDEN response optionally stating the reason for this
+ * response code in the {@link AuthConstants#X_REASON} header. The value for the
+ * {@link AuthConstants#X_REASON} header is taken from
+ * {@link AuthenticationHandler#FAILURE_REASON} request attribute if set.
+ *
+ * This method just overwrites the response status to 403/FORBIDDEN, adds
+ * the {@link AuthConstants#X_REASON} header and sends the reason as result
+ * back. Any other response headers set before calling this methods are
+ * preserved and sent along with the response.
+ *
+ * @param request The request object
+ * @param response The response object
+ * @throws IllegalStateException if the response has already been committed
+ */
+ public static void sendInvalid(final HttpServletRequest request,
+ final HttpServletResponse response) {
+ checkAndReset(response);
+ try {
+ response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+
+ Object reason = request.getAttribute(AuthenticationHandler.FAILURE_REASON);
+ Object reasonCode = request.getAttribute(AuthenticationHandler.FAILURE_REASON_CODE);
+ if (reason != null) {
+ response.setHeader(AuthConstants.X_REASON, reason.toString());
+ if ( reasonCode != null ) {
+ response.setHeader(AuthConstants.X_REASON_CODE, reasonCode.toString());
+ }
+ response.setContentType("text/plain");
+ response.setCharacterEncoding("UTF-8");
+ response.getWriter().println(reason);
+ }
+
+ response.flushBuffer();
+ } catch (IOException ioe) {
+ getLog().error("Failed to send 403/Forbidden response", ioe);
+ }
+ }
+
+ /**
+ * Check if the request is for this authentication handler.
+ *
+ * @param request the current request
+ * @return true if the referer matches this handler, or false otherwise
+ */
+ public static boolean checkReferer(HttpServletRequest request, String loginForm) {
+ //SLING-2165: if a Referer header is supplied check if it matches the login path for this handler
+ if ("POST".equals(request.getMethod())) {
+ String referer = request.getHeader("Referer");
+ if (referer != null) {
+ String expectedPath = String.format("%s%s", request.getContextPath(), loginForm);
+ try {
+ URL uri = new URL(referer);
+ if (!expectedPath.equals(uri.getPath())) {
+ //not for this selector, so let the next one handle it.
+ return false;
+ }
+ } catch (MalformedURLException e) {
+ getLog().debug("Failed to parse the referer value for the login form " + loginForm, e);
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns true if the given redirect target is
+ * valid according to the following list of requirements:
+ *
+ *
The target is neither null nor an empty
+ * string
+ *
The target is not an URL which is identified by the
+ * character sequence :// separating the scheme from the host
+ *
The target is normalized such that it contains no
+ * consecutive slashes and no path segment contains a single or double dot
+ *
The target must be prefixed with the servlet context
+ * path
+ *
If a ResourceResolver is available as a request
+ * attribute the target (without the servlet context path
+ * prefix) must resolve to an existing resource
+ *
If a ResourceResolver is not available as a
+ * request attribute the target must be an absolute path
+ * starting with a slash character does not contain any of the characters
+ * <, >, ', or "
+ * in plain or URL encoding
+ *
+ *
+ * If any of the conditions does not hold, the method returns
+ * false and logs a warning level message with the
+ * org.apache.sling.auth.core.AuthUtil logger.
+ *
+ * @param request Providing the ResourceResolver attribute and
+ * the context to resolve the resource from the
+ * target. This may be null which
+ * causes the target to not be validated with a
+ * ResoureResolver
+ * @param target The redirect target to validate. This path must be
+ * prefixed with the request's servlet context path.
+ * @return true if the redirect target can be considered valid
+ */
+ public static boolean isRedirectValid(final HttpServletRequest request, final String target) {
+ if (target == null || target.length() == 0) {
+ getLog().warn("isRedirectValid: Redirect target must not be empty or null");
+ return false;
+ }
+
+ try {
+ new URI(target);
+ } catch (URISyntaxException e) {
+ getLog().warn("isRedirectValid: Redirect target '{}' contains illegal characters", target);
+ return false;
+ }
+
+ if (target.contains("://")) {
+ getLog().warn("isRedirectValid: Redirect target '{}' must not be an URL", target);
+ return false;
+ }
+
+ if (target.contains("//") || target.contains("/../") || target.contains("/./") || target.endsWith("/.")
+ || target.endsWith("/..")) {
+ getLog().warn("isRedirectValid: Redirect target '{}' is not normalized", target);
+ return false;
+ }
+
+ final String ctxPath = getContextPath(request);
+ if (ctxPath.length() > 0 && !target.startsWith(ctxPath)) {
+ getLog().warn("isRedirectValid: Redirect target '{}' does not start with servlet context path '{}'",
+ target, ctxPath);
+ return false;
+ }
+
+ // special case of requesting the servlet context root path
+ if (ctxPath.length() == target.length()) {
+ return true;
+ }
+
+ final String localTarget = target.substring(ctxPath.length());
+ if (!localTarget.startsWith("/")) {
+ getLog().warn(
+ "isRedirectValid: Redirect target '{}' without servlet context path '{}' must be an absolute path",
+ target, ctxPath);
+ return false;
+ }
+
+ final int query = localTarget.indexOf('?');
+ final String path = (query > 0) ? localTarget.substring(0, query) : localTarget;
+
+ ResourceResolver resolver = getResourceResolver(request);
+ if (resolver != null) {
+ // assume all is fine if the path resolves to a resource
+ if (!ResourceUtil.isNonExistingResource(resolver.resolve(request, path))) {
+ return true;
+ }
+
+ // not resolving to a resource, check for illegal characters
+ }
+
+ final Pattern illegal = Pattern.compile("[<>'\"]");
+ if (illegal.matcher(path).find()) {
+ getLog().warn("isRedirectValid: Redirect target '{}' must not contain any of <>'\"", target);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the context path from the request or an empty string if the
+ * request is null.
+ */
+ private static String getContextPath(final HttpServletRequest request) {
+ if (request != null) {
+ return request.getContextPath();
+ }
+ return "";
+ }
+
+ /**
+ * Returns the resource resolver set as the
+ * {@link AuthenticationSupport#REQUEST_ATTRIBUTE_RESOLVER} request
+ * attribute or null if the request object is null
+ * or the resource resolver is not present.
+ */
+ private static ResourceResolver getResourceResolver(final HttpServletRequest request) {
+ if (request != null) {
+ return (ResourceResolver) request.getAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER);
+ }
+ return null;
+ }
+
+ /**
+ * Returns true if the given request can be assumed to be sent
+ * by a client browser such as Firefix, Internet Explorer, etc.
+ *
+ * This method inspects the User-Agent header and returns
+ * true if the header contains the string Mozilla (known
+ * to be contained in Firefox, Internet Explorer, WebKit-based browsers
+ * User-Agent) or Opera (known to be contained in the Opera
+ * User-Agent).
+ *
+ * @param request The request to inspect
+ * @return true if the request is assumed to be sent by a
+ * browser.
+ */
+ public static boolean isBrowserRequest(final HttpServletRequest request) {
+ final String userAgent = request.getHeader(USER_AGENT);
+ if (userAgent != null && (userAgent.contains(BROWSER_CLASS_MOZILLA) || userAgent.contains(BROWSER_CLASS_OPERA))) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the request is to be considered an AJAX
+ * request placed using the XMLHttpRequest browser host object.
+ * Currently a request is considered an AJAX request if the client sends the
+ * X-Requested-With request header set to XMLHttpRequest
+ * .
+ *
+ * @param request The current request
+ * @return true if the request can be considered an AJAX
+ * request.
+ */
+ public static boolean isAjaxRequest(final HttpServletRequest request) {
+ return XML_HTTP_REQUEST.equals(request.getHeader(X_REQUESTED_WITH));
+ }
+
+ /**
+ * Checks whether the response has already been committed. If so an
+ * IllegalStateException is thrown. Otherwise the response
+ * buffer is cleared leaving any headers and status already set untouched.
+ *
+ * @param response The response to check and reset.
+ * @throws IllegalStateException if the response has already been committed
+ */
+ private static void checkAndReset(final HttpServletResponse response) {
+ if (response.isCommitted()) {
+ throw new IllegalStateException("Response is already committed");
+ }
+ response.resetBuffer();
+ }
+
+ /**
+ * Helper method returning a org.apache.sling.auth.core.AuthUtil logger.
+ */
+ private static Logger getLog() {
+ return LoggerFactory.getLogger("org.apache.sling.auth.core.AuthUtil");
+ }
+}
diff --git a/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/AuthenticationSupport.java b/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/AuthenticationSupport.java
new file mode 100644
index 000000000..7257110fd
--- /dev/null
+++ b/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/AuthenticationSupport.java
@@ -0,0 +1,122 @@
+/*
+ * 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.auth.core;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.osgi.annotation.versioning.ProviderType;
+
+/**
+ * The AuthenticationSupport provides the service API used to
+ * implement the HttpContext.handleSecurity method as defined in
+ * the OSGi Http Service specification.
+ *
+ * Bundles registering servlets and/or resources with custom
+ * HttpContext implementations may implement the
+ * handleSecurity method using this service. The
+ * {@link #handleSecurity(HttpServletRequest, HttpServletResponse)} method
+ * implemented by this service exactly implements the specification of the
+ * HttpContext.handleSecurity method.
+ *
+ * A simple implementation of the HttpContext interface based on
+ * this could be (using SCR JavaDoc tags of the Maven SCR Plugin) :
+ *
+ *
+ * This interface is implemented by this bundle and is not intended to be
+ * implemented by client bundles.
+ */
+@ProviderType
+public interface AuthenticationSupport {
+
+ /**
+ * The name under which this service is registered.
+ */
+ static final String SERVICE_NAME = "org.apache.sling.auth.core.AuthenticationSupport";
+
+ /**
+ * The name of the request attribute set by the
+ * {@link #handleSecurity(HttpServletRequest, HttpServletResponse)} method
+ * if authentication succeeds and true is returned.
+ *
+ * The request attribute is set to a Sling ResourceResolver
+ * attached to the JCR repository using the credentials provided by the
+ * request.
+ */
+ static final String REQUEST_ATTRIBUTE_RESOLVER = "org.apache.sling.auth.core.ResourceResolver";
+
+ /**
+ * The name of the request parameter indicating where to redirect to after
+ * successful authentication (and optional impersonation). This parameter is
+ * respected if either anonymous authentication or regular authentication
+ * succeed.
+ *
+ * If authentication fails, either because the credentials are wrong or
+ * because anonymous authentication fails or because anonymous
+ * authentication is not allowed for the request, the parameter is ignored
+ * and the
+ * {@link org.apache.sling.auth.core.spi.AuthenticationHandler#requestCredentials(HttpServletRequest, HttpServletResponse)}
+ * method is called to request authentication.
+ */
+ static final String REDIRECT_PARAMETER = "sling.auth.redirect";
+
+ /**
+ * Handles security on behalf of a custom OSGi Http Service
+ * HttpContext instance extracting credentials from the request
+ * using any registered
+ * {@link org.apache.sling.auth.core.spi.AuthenticationHandler} services.
+ * If the credentials can be extracted and used to log into the JCR
+ * repository this method sets the request attributes required by the OSGi
+ * Http Service specification plus the {@link #REQUEST_ATTRIBUTE_RESOLVER}
+ * attribute.
+ *
+ * @param request The HTTP request to be authenticated
+ * @param response The HTTP response to send any response to in case of
+ * problems.
+ * @return true if authentication succeeded and the request
+ * attributes are set. If false is returned the request
+ * is immediately terminated and no request attributes are set.
+ */
+ boolean handleSecurity(HttpServletRequest request,
+ HttpServletResponse response);
+
+}
diff --git a/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/impl/AbstractAuthenticationHandlerHolder.java b/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/impl/AbstractAuthenticationHandlerHolder.java
new file mode 100644
index 000000000..91f63ccdf
--- /dev/null
+++ b/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/impl/AbstractAuthenticationHandlerHolder.java
@@ -0,0 +1,198 @@
+/*
+ * 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.auth.core.impl;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.auth.core.spi.AuthenticationFeedbackHandler;
+import org.apache.sling.auth.core.spi.AuthenticationHandler;
+import org.apache.sling.auth.core.spi.AuthenticationInfo;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * The AbstractAuthenticationHandlerHolder is a base class to
+ * represent authentication handlers (both legacy and new ones) for use in the
+ * {@link SlingAuthenticator}.
+ */
+public abstract class AbstractAuthenticationHandlerHolder extends
+ PathBasedHolder implements AuthenticationHandler {
+
+ protected AbstractAuthenticationHandlerHolder(final String fullPath,
+ final ServiceReference serviceReference) {
+ super(fullPath, serviceReference);
+ }
+
+ /**
+ * Sets the {@link AuthenticationHandler#PATH_PROPERTY} request attribute to
+ * this {@link PathBasedHolder#fullPath} and calls the
+ * {@link #extractCredentials(HttpServletRequest, HttpServletResponse)} to
+ * have the credentials extracted from the request.
+ *
+ * @param request the current request
+ * @param response the current response
+ * @return the result of calling
+ * {@link #doExtractCredentials(HttpServletRequest, HttpServletResponse)}
+ */
+ public final AuthenticationInfo extractCredentials(
+ HttpServletRequest request, HttpServletResponse response) {
+
+ final Object oldPathAttr = setPath(request);
+ try {
+ return doExtractCredentials(request, response);
+ } finally {
+ resetPath(request, oldPathAttr);
+ }
+
+ }
+
+ /**
+ * Sets the {@link AuthenticationHandler#PATH_PROPERTY} request attribute to
+ * this {@link PathBasedHolder#fullPath} and calls the
+ * {@link #doRequestCredentials(HttpServletRequest, HttpServletResponse)} to
+ * have the credentials requested from the client.
+ *
+ * @param request the current request
+ * @param response the current response
+ * @return the result of calling
+ * {@link #doRequestCredentials(HttpServletRequest, HttpServletResponse)}
+ * @throws IOException if an error occurs interacting with the client
+ */
+ public final boolean requestCredentials(HttpServletRequest request,
+ HttpServletResponse response) throws IOException {
+ final Object oldPathAttr = setPath(request);
+ try {
+ return doRequestCredentials(request, response);
+ } finally {
+ resetPath(request, oldPathAttr);
+ }
+ }
+
+ /**
+ * Sets the {@link AuthenticationHandler#PATH_PROPERTY} request attribute to
+ * this {@link PathBasedHolder#fullPath} and calls the
+ * {@link #doDropCredentials(HttpServletRequest, HttpServletResponse)} to
+ * have the credentials dropped by the held authentication handler.
+ *
+ * @param request the current request
+ * @param response the current response
+ * @throws IOException if an error occurs interacting with the client
+ */
+ public final void dropCredentials(HttpServletRequest request,
+ HttpServletResponse response) throws IOException {
+ final Object oldPathAttr = setPath(request);
+ try {
+ doDropCredentials(request, response);
+ } finally {
+ resetPath(request, oldPathAttr);
+ }
+ }
+
+ // --------- API to be implemented
+
+ /**
+ * Returns a feedback handler provided by the authentication handler held by
+ * this instance or null if none is provided.
+ */
+ protected abstract AuthenticationFeedbackHandler getFeedbackHandler();
+
+ /**
+ * Calls the actual authentication handler to extract the credentials from
+ * the request.
+ *
+ * @param request The current request
+ * @param response The current response
+ * @return as returned from the called authentication handler
+ * @see #extractCredentials(HttpServletRequest, HttpServletResponse)
+ */
+ protected abstract AuthenticationInfo doExtractCredentials(
+ HttpServletRequest request, HttpServletResponse response);
+
+ /**
+ * Calls the actual authentication handler to request the credentials from
+ * the client.
+ *
+ * @param request The current request
+ * @param response The current response
+ * @return as returned from the called authentication handler
+ * @throws IOException if an error occurs sending back any response to the
+ * client.
+ * @see #requestCredentials(HttpServletRequest, HttpServletResponse)
+ */
+ protected abstract boolean doRequestCredentials(HttpServletRequest request,
+ HttpServletResponse response) throws IOException;
+
+ /**
+ * Calls the actual authentication handler to request the credentials from
+ * the client.
+ *
+ * @param request The current request
+ * @param response The current response
+ * @throws IOException if an error occurs sending back any response to the
+ * client.
+ * @see #dropCredentials(HttpServletRequest, HttpServletResponse)
+ */
+ protected abstract void doDropCredentials(HttpServletRequest request,
+ HttpServletResponse response) throws IOException;
+
+ // ---------- internal
+
+ /**
+ * Sets the {@link PathBasedHolder#fullPath} as the
+ * {@link AuthenticationHandler#PATH_PROPERTY} request attribute.
+ */
+ private Object setPath(final HttpServletRequest request) {
+ return setRequestAttribute(request,
+ AuthenticationHandler.PATH_PROPERTY, fullPath);
+ }
+
+ /**
+ * Sets the given odlValue as the
+ * {@link AuthenticationHandler#PATH_PROPERTY} request attribute.
+ */
+ private void resetPath(final HttpServletRequest request, Object oldValue) {
+ setRequestAttribute(request, AuthenticationHandler.PATH_PROPERTY,
+ oldValue);
+ }
+
+ /**
+ * Sets the named request attribute to the new value and returns the
+ * previous value.
+ *
+ * @param request The request object whose attribute is to be set.
+ * @param name The name of the attribute to be set.
+ * @param value The new value of the attribute. If this is null
+ * the attribute is actually removed from the request.
+ * @return The previous value of the named request attribute or
+ * null if it was not set.
+ */
+ private static Object setRequestAttribute(HttpServletRequest request,
+ String name, Object value) {
+ Object oldValue = request.getAttribute(name);
+ if (value == null) {
+ request.removeAttribute(name);
+ } else {
+ request.setAttribute(name, value);
+ }
+ return oldValue;
+ }
+
+}
\ No newline at end of file
diff --git a/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/impl/AuthenticationHandlerHolder.java b/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/impl/AuthenticationHandlerHolder.java
new file mode 100644
index 000000000..066362b2f
--- /dev/null
+++ b/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/impl/AuthenticationHandlerHolder.java
@@ -0,0 +1,154 @@
+/*
+ * 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.auth.core.impl;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.auth.core.AuthConstants;
+import org.apache.sling.auth.core.AuthUtil;
+import org.apache.sling.auth.core.spi.AuthenticationFeedbackHandler;
+import org.apache.sling.auth.core.spi.AuthenticationHandler;
+import org.apache.sling.auth.core.spi.AuthenticationInfo;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * The AuthenticationHandlerHolder class represents an
+ * authentication handler service in the internal data structure of the
+ * {@link SlingAuthenticator}.
+ */
+final class AuthenticationHandlerHolder extends
+ AbstractAuthenticationHandlerHolder {
+
+ // the actual authentication handler
+ private final AuthenticationHandler handler;
+
+ // the supported authentication type of the handler
+ private final String authType;
+
+ // whether requestCredentials only for browsers
+ private final boolean browserOnlyRequestCredentials;
+
+ AuthenticationHandlerHolder(final String fullPath,
+ final AuthenticationHandler handler,
+ final ServiceReference serviceReference) {
+ super(fullPath, serviceReference);
+
+ final String browserOnly = PropertiesUtil.toString(serviceReference.getProperty(AuthConstants.AUTH_HANDLER_BROWSER_ONLY), null);
+
+ // assign the fields
+ this.handler = handler;
+ this.authType = PropertiesUtil.toString(serviceReference.getProperty(TYPE_PROPERTY), null);
+ this.browserOnlyRequestCredentials = "true".equalsIgnoreCase(browserOnly)
+ || "yes".equalsIgnoreCase(browserOnly);
+ }
+
+ @Override
+ protected AuthenticationFeedbackHandler getFeedbackHandler() {
+ if (handler instanceof AuthenticationFeedbackHandler) {
+ return (AuthenticationFeedbackHandler) handler;
+ }
+ return null;
+ }
+
+ @Override
+ public AuthenticationInfo doExtractCredentials(HttpServletRequest request,
+ HttpServletResponse response) {
+ return handler.extractCredentials(request, response);
+ }
+
+ @Override
+ public boolean doRequestCredentials(HttpServletRequest request,
+ HttpServletResponse response) throws IOException {
+
+ // call handler if ok by its authentication type
+ if (doesRequestCredentials(request)) {
+ return handler.requestCredentials(request, response);
+ }
+
+ // no credentials have been requested
+ return false;
+ }
+
+ @Override
+ public void doDropCredentials(HttpServletRequest request,
+ HttpServletResponse response) throws IOException {
+ handler.dropCredentials(request, response);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+
+ // equality is the base class equality (based on the fullpath)
+ // and the encapsulated holders being the same.
+ if (super.equals(obj)) {
+ if (obj.getClass() == getClass()) {
+ AuthenticationHandlerHolder other = (AuthenticationHandlerHolder) obj;
+ return other.handler == handler;
+ }
+ }
+
+ // handlers are not the same, so the holders are not the same
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return handler.toString();
+ }
+
+ /**
+ * Returns true if the requestCredentials method
+ * of the held authentication handler should be called or not:
+ *
+ *
If the handler handles all clients or the request is assumed to be
+ * coming from a browser
+ *
If the authentication handler is registered without an authentication
+ * type
+ *
If the sling:authRequestLogin request parameter or
+ * attribute is not set
+ *
If the sling:authRequestLogin is set to the same value
+ * as the authentication type of the held authentication handler.
+ *
+ *
+ * Otherwise false is returned and the
+ * requestCredentials method is not called.
+ *
+ * @param request The request object providing the
+ * sling:authRequestLogin parameter
+ * @return true if the requestCredentials method
+ * should be called.
+ */
+ private boolean doesRequestCredentials(final HttpServletRequest request) {
+
+ if (browserOnlyRequestCredentials && !AuthUtil.isBrowserRequest(request)) {
+ return false;
+ }
+
+ if (authType == null) {
+ return true;
+ }
+
+ final String requestLogin = AuthUtil.getAttributeOrParameter(request, REQUEST_LOGIN_PARAMETER, null);
+ return requestLogin == null || authType.equals(requestLogin);
+ }
+}
\ No newline at end of file
diff --git a/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/impl/AuthenticationRequirementHolder.java b/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/impl/AuthenticationRequirementHolder.java
new file mode 100644
index 000000000..04d8c2196
--- /dev/null
+++ b/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/impl/AuthenticationRequirementHolder.java
@@ -0,0 +1,61 @@
+/*
+ * 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.auth.core.impl;
+
+import org.osgi.framework.ServiceReference;
+
+class AuthenticationRequirementHolder extends PathBasedHolder {
+
+ private final boolean requiresAuthentication;
+
+ static AuthenticationRequirementHolder fromConfig(final String config,
+ final ServiceReference> serviceReference) {
+ if (config == null || config.length() == 0) {
+ throw new IllegalArgumentException(
+ "Configuration must not be null or empty");
+ }
+
+ final boolean required;
+ final String path;
+ if (config.startsWith("+")) {
+ required = true;
+ path = config.substring(1);
+ } else if (config.startsWith("-")) {
+ required = false;
+ path = config.substring(1);
+ } else {
+ required = true;
+ path = config;
+ }
+
+ return new AuthenticationRequirementHolder(path, required,
+ serviceReference);
+ }
+
+ AuthenticationRequirementHolder(final String fullPath,
+ final boolean requiresAuthentication,
+ final ServiceReference> serviceReference) {
+ super(fullPath, serviceReference);
+ this.requiresAuthentication = requiresAuthentication;
+ }
+
+ boolean requiresAuthentication() {
+ return requiresAuthentication;
+ }
+}
diff --git a/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/impl/AuthenticatorWebConsolePlugin.java b/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/impl/AuthenticatorWebConsolePlugin.java
new file mode 100644
index 000000000..8ae143b73
--- /dev/null
+++ b/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/impl/AuthenticatorWebConsolePlugin.java
@@ -0,0 +1,146 @@
+/*
+ * 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.auth.core.impl;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.api.request.ResponseUtil;
+
+@SuppressWarnings("serial")
+public class AuthenticatorWebConsolePlugin extends HttpServlet {
+
+ private final SlingAuthenticator slingAuthenticator;
+
+ String getLabel() {
+ return "slingauth";
+ }
+
+ String getTitle() {
+ return "Authenticator";
+ }
+
+ public AuthenticatorWebConsolePlugin(
+ final SlingAuthenticator slingAuthenticator) {
+ this.slingAuthenticator = slingAuthenticator;
+ }
+
+ @Override
+ protected void service(HttpServletRequest req, HttpServletResponse resp)
+ throws ServletException, IOException {
+ // only handle GET requests, ensure no error message for other requests
+ if ("GET".equals(req.getMethod()) || "HEAD".equals(req.getMethod())) {
+ super.service(req, resp);
+ }
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+ throws IOException {
+
+ PrintWriter pw = resp.getWriter();
+
+ pw.println("
");
+ }
+}
diff --git a/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/impl/HttpBasicAuthenticationHandler.java b/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/impl/HttpBasicAuthenticationHandler.java
new file mode 100644
index 000000000..76abd9e51
--- /dev/null
+++ b/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/impl/HttpBasicAuthenticationHandler.java
@@ -0,0 +1,359 @@
+/*
+ * 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.auth.core.impl;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.sling.auth.core.AuthUtil;
+import org.apache.sling.auth.core.spi.AuthenticationHandler;
+import org.apache.sling.auth.core.spi.AuthenticationInfo;
+import org.apache.sling.auth.core.spi.DefaultAuthenticationFeedbackHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The HttpBasicAuthenticationHandler class supports plain old HTTP
+ * Basic authentication. While {@link #extractCredentials(HttpServletRequest)}
+ * always accesses the header if called and if present, the
+ * {@link #requestCredentials(HttpServletRequest, HttpServletResponse)} and
+ * {@link #dropCredentials(HttpServletRequest, HttpServletResponse)} methods
+ * must be explicitly enabled to send back a 401/UNAUTHORIZED reply to force the
+ * client into HTTP Basic authentication.
+ *
+ * Being able to just extract credentials but not actively request them provides
+ * an easy way for tools (like cURL) or libraries (like Apache HttpCLient) to
+ * preemptively authenticate with HTTP Basic authentication.
+ */
+class HttpBasicAuthenticationHandler extends
+ DefaultAuthenticationFeedbackHandler implements AuthenticationHandler {
+
+ private static final String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
+
+ private static final String HEADER_AUTHORIZATION = "Authorization";
+
+ private static final String AUTHENTICATION_SCHEME_BASIC = "Basic";
+
+ /** default log */
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ /** The realm to send back with the 401 response */
+ private final String realm;
+
+ /**
+ * Whether this authentication handler is fully enabled and sends back 401
+ * responses from the
+ * {@link #requestCredentials(HttpServletRequest, HttpServletResponse)} and
+ * {@link #dropCredentials(HttpServletRequest, HttpServletResponse)}
+ * methods.
+ */
+ private final boolean fullSupport;
+
+ HttpBasicAuthenticationHandler(final String realm,
+ final boolean fullSupport) {
+ this.realm = realm;
+ this.fullSupport = fullSupport;
+ }
+
+ // ----------- AuthenticationHandler interface ----------------------------
+
+ /**
+ * Returns the credential present within in an HTTP Basic authentication
+ * header or null if no credentials are provided and the
+ * {@link AuthenticationHandler#REQUEST_LOGIN_PARAMETER} is neither set as a
+ * request parameter nor as a request attribute.
+ *
+ * If the {@link AuthenticationHandler#REQUEST_LOGIN_PARAMETER} is set as a
+ * request parameter or request attribute, a 401 response is sent to the
+ * client and the method returns {@link AuthenticationInfo#DOING_AUTH} to
+ * indicate that the handler has started its own credentials requesting.
+ *
+ * @param request The request object containing the information for the
+ * authentication.
+ * @param response The response object which may be used to send the
+ * information on the request failure to the user.
+ * @return A valid Credentials instance identifying the request user,
+ * DOING_AUTH if the handler is in an authentication transaction with
+ * the client or null if the request does not contain authentication
+ * information. In case of DOING_AUTH, the method has sent back a
+ * 401 requesting the client to provide credentials.
+ */
+ public AuthenticationInfo extractCredentials(HttpServletRequest request,
+ HttpServletResponse response) {
+
+ // extract credentials and return
+ AuthenticationInfo info = this.extractCredentials(request);
+ if (info != null) {
+ return info;
+ }
+
+ // no credentials, check whether the client wants to login
+ if (forceAuthentication(request, response)) {
+ return AuthenticationInfo.DOING_AUTH;
+ }
+
+ // no special header, so we will not authenticate here
+ return null;
+ }
+
+ /**
+ * Called by the SlingAuthenticator.login method in case no other
+ * authentication handler was willing to request credentials from the
+ * client. In this case this HTTP Basic authentication handler will send
+ * back a {@link #sendUnauthorized(HttpServletResponse) 401 response} to
+ * request HTTP Basic authentication from the client if full support has
+ * been configured in the
+ * {@link #HttpBasicAuthenticationHandler(String, boolean) constructor}
+ *
+ * @param request The request object
+ * @param response The response object to which to send the request
+ * @return true if full support is enabled and the 401 response
+ * could be sent. If full support is not enabled false
+ * is always returned.
+ */
+ public boolean requestCredentials(HttpServletRequest request,
+ HttpServletResponse response) {
+ return fullSupport ? sendUnauthorized(response) : false;
+ }
+
+ /**
+ * Sends a 401/UNAUTHORIZED response if the request has an Authorization
+ * header and if this handler is configured to actually send this response
+ * in response to a request to drop the credentials; that is if full support
+ * has been enabled in the
+ * {@link #HttpBasicAuthenticationHandler(String, boolean) constructor}.
+ *
+ * Note, that sending a 401/UNAUTHORIZED response is generally the only save
+ * means to remove HTTP Basic credentials from a browser's cache. Yet, the
+ * nasty side-effect is that the browser's login form is displayed as a
+ * reaction to the 401/UNAUTHORIZED response.
+ */
+ public void dropCredentials(HttpServletRequest request,
+ HttpServletResponse response) {
+ if (fullSupport && request.getHeader(HEADER_AUTHORIZATION) != null) {
+ sendUnauthorized(response);
+ }
+ }
+
+ /**
+ * Called if the credentials extracted by the
+ * {@link #extractCredentials(HttpServletRequest, HttpServletResponse)}
+ * method are not valid and sends back a 401/UNAUTHORIZED response
+ * requesting the credentials again.
+ *
+ * The only way to get a browser (or a client in general) into forgetting
+ * the current credentials and sending different credentials is sending back
+ * such a response. Otherwise the browser sends the same credentials over
+ * and over again.
+ *
+ * The assumption of this method unconditionally sending back the
+ * 401/UNAUTHORIZED response is that this method here is only called if the
+ * request actually provided invalid HTTP Basic credentials.
+ *
+ * If the request is a
+ * {@link AuthUtil#isValidateRequest(HttpServletRequest) validation request}
+ * this method actually does nothing to allow for the expected 403/FORBIDDEN
+ * response to be sent.
+ */
+ @Override
+ public void authenticationFailed(HttpServletRequest request, HttpServletResponse response,
+ AuthenticationInfo authInfo) {
+ if (!AuthUtil.isValidateRequest(request)) {
+ sendUnauthorized(response);
+ }
+ }
+
+ /**
+ * Returns true if the {@link #REQUEST_LOGIN_PARAMETER} parameter or request
+ * attribute is set to any non-null value.
+ *
+ * This method always returns true if the parameter or request
+ * attribute is set regardless of its value because the client indicated it
+ * wanted to login but no authentication handler was willing to actually
+ * handle this request. So as a last fallback this handler request HTTP
+ * Basic Credentials.
+ *
+ * @param request The request object providing the parameter or attribute.
+ * @return true if the
+ * {@link AuthenticationHandler#REQUEST_LOGIN_PARAMETER} parameter
+ * or attribute is set to any value.
+ */
+ private boolean isLoginRequested(HttpServletRequest request) {
+ return AuthUtil.getAttributeOrParameter(request, REQUEST_LOGIN_PARAMETER, null) != null;
+ }
+
+ /**
+ * If the {@link #REQUEST_LOGIN_PARAMETER} parameter is set this method
+ * sends status 401 (Unauthorized) with a
+ * WWW-Authenticate requesting standard HTTP header
+ * authentication with the Basic scheme and the configured
+ * realm name. If the response is already committed, an error message is
+ * logged but the 401 status is not sent.
+ *
+ * false is returned if the request parameter is not set, if
+ * the response is already committed or if an error occurred sending the
+ * status response. The latter two situations are logged as errors.
+ *
+ * @param request The request object
+ * @param response The response object to which to send the request
+ * @return true if the 401/UNAUTHORIZED method has successfully
+ * been sent.
+ */
+ private boolean forceAuthentication(HttpServletRequest request,
+ HttpServletResponse response) {
+
+ // presume 401/UNAUTHORIZED has not been sent
+ boolean authenticationForced = false;
+
+ if (isLoginRequested(request)) {
+
+ authenticationForced = sendUnauthorized(response);
+
+ } else {
+
+ log.debug(
+ "forceAuthentication: Not forcing authentication because request parameter {} is not set",
+ REQUEST_LOGIN_PARAMETER);
+
+ }
+
+ // true if 401/UNAUTHORIZED has been sent, false otherwise
+ return authenticationForced;
+ }
+
+ /**
+ * Sends status 401 (Unauthorized) with a
+ * WWW-Authenticate requesting standard HTTP header
+ * authentication with the Basic scheme and the configured
+ * realm name.
+ *
+ * @param response The response object to which to send the request
+ * @return true if the 401/UNAUTHORIZED method has successfully
+ * been sent and the response has been committed.
+ */
+ boolean sendUnauthorized(HttpServletResponse response) {
+
+ if (response.isCommitted()) {
+
+ log.error("sendUnauthorized: Cannot send 401/UNAUTHORIZED; response is already committed");
+
+ } else {
+
+ response.resetBuffer();
+
+ /*
+ * TODO: Check whether we have to redirect
+ * If this is a GET request not targeted at the registration path
+ * for which this handler is selected we have to redirect to the
+ * registration path using either the provided resource attribute
+ * or parameter or the current URL as the "resource" parameter
+ * for the redirect and also setting the "sling:authRequestLogin"
+ * parameter to "BASIC" to get the 401 response for the registration
+ * path and redirect back to actual path afterwards.
+ */
+
+ // just set the status because this may be called as part of an
+ // error handler in which case sendError would result in an error
+ // handler loop and thus be ignored.
+ response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+ response.setHeader(HEADER_WWW_AUTHENTICATE,
+ AUTHENTICATION_SCHEME_BASIC + " realm=\"" + this.realm + "\"");
+
+ try {
+ response.flushBuffer();
+ return true;
+ } catch (IOException ioe) {
+ log.error("sendUnauthorized: Failed requesting authentication",
+ ioe);
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "HTTP Basic Authentication Handler ("
+ + (fullSupport ? "enabled" : "preemptive") + ")";
+ }
+
+ // ---------- internal -----------------------------------------------------
+
+ /**
+ * Extract the Base64 authentication string from the request
+ */
+ protected AuthenticationInfo extractCredentials(HttpServletRequest request) {
+
+ // Return immediately if the header is missing
+ String authHeader = request.getHeader(HEADER_AUTHORIZATION);
+ if (authHeader == null || authHeader.length() == 0) {
+ return null;
+ }
+
+ // Get the authType (Basic, Digest) and authInfo (user/password) from
+ // the header
+ authHeader = authHeader.trim();
+ int blank = authHeader.indexOf(' ');
+ if (blank <= 0) {
+ return null;
+ }
+ String authType = authHeader.substring(0, blank);
+ String authInfo = authHeader.substring(blank).trim();
+
+ // Check whether authorization type matches
+ if (!authType.equalsIgnoreCase(AUTHENTICATION_SCHEME_BASIC)) {
+ return null;
+ }
+
+ // Base64 decode and split on colon
+
+ // we cannot use default base64, since we need iso encoding
+ // (nb: ISO-8859-1 is required as per API spec to be available)
+ String decoded;
+ try {
+ byte[] encoded = authInfo.getBytes("ISO-8859-1");
+ byte[] bytes = Base64.decodeBase64(encoded);
+ decoded = new String(bytes, "ISO-8859-1");
+ } catch (UnsupportedEncodingException uee) {
+ // unexpected
+ log.error(
+ "extractAuthentication: Cannot en/decode authentication info",
+ uee);
+ return null;
+ }
+
+ final int colIdx = decoded.indexOf(':');
+ final String userId;
+ final char[] password;
+ if (colIdx < 0) {
+ userId = decoded;
+ password = new char[0];
+ } else {
+ userId = decoded.substring(0, colIdx);
+ password = decoded.substring(colIdx + 1).toCharArray();
+ }
+
+ return new AuthenticationInfo(HttpServletRequest.BASIC_AUTH, userId,
+ password);
+ }
+}
\ No newline at end of file
diff --git a/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/impl/LoginServlet.java b/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/impl/LoginServlet.java
new file mode 100644
index 000000000..bbf7409f9
--- /dev/null
+++ b/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/impl/LoginServlet.java
@@ -0,0 +1,136 @@
+/*
+ * 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.auth.core.impl;
+
+import java.io.IOException;
+
+import javax.servlet.Servlet;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.auth.Authenticator;
+import org.apache.sling.api.auth.NoAuthenticationHandlerException;
+import org.apache.sling.api.servlets.SlingAllMethodsServlet;
+import org.apache.sling.auth.core.AuthUtil;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicy;
+import org.osgi.service.component.propertytypes.ServiceDescription;
+import org.osgi.service.component.propertytypes.ServiceVendor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The LoginServlet lets the Authenticator do the login.
+ */
+@Component(service = Servlet.class,
+ property = {
+ "sling.servlet.methods=GET",
+ "sling.servlet.methods=POST",
+ "sling.servlet.paths=" + LoginServlet.SERVLET_PATH
+ })
+@ServiceDescription("Authenticator Login Servlet")
+@ServiceVendor("The Apache Software Foundation")
+public class LoginServlet extends SlingAllMethodsServlet {
+
+ /** serialization UID */
+ private static final long serialVersionUID = -8797082194403667968L;
+
+ /** default log */
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ @Reference(policy = ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.OPTIONAL)
+ private volatile Authenticator authenticator;
+
+ /**
+ * The servlet is registered on this path, and the authenticator allows any
+ * requests to that path, without authentication
+ */
+ public static final String SERVLET_PATH = "/system/sling/login";
+
+ @Override
+ protected void service(SlingHttpServletRequest request,
+ SlingHttpServletResponse response) throws IOException {
+
+
+ // if the request is logged in and the resource is not set (such
+ // as when requesting /system/sling/login from the browser with the
+ // browser sending credentials) or the resource is set to the login
+ // servlet as a result of authenticating after providing credentials
+ // through the login servlet), redirect to root now assuming we are
+ // authenticated.
+ if (request.getAuthType() != null) {
+ final String resourcePath = AuthUtil.getLoginResource(request, null);
+ if (isSelf(resourcePath)) {
+ String redirectTarget = request.getContextPath() + "/";
+ log.warn(
+ "doGet: Redirecting to {} to prevent login loop for resource {}",
+ redirectTarget, resourcePath);
+ response.sendRedirect(redirectTarget);
+ return;
+ }
+ }
+
+ Authenticator authenticator = this.authenticator;
+ if (authenticator != null) {
+ try {
+
+ // set the login resource to select the authenticator
+ AuthUtil.setLoginResourceAttribute(request, null);
+ authenticator.login(request, response);
+ return;
+
+ } catch (IllegalStateException ise) {
+
+ log.error("doGet: Response already committed, cannot login");
+ return;
+
+ } catch (NoAuthenticationHandlerException nahe) {
+
+ log.error("doGet: No AuthenticationHandler to login registered");
+
+ }
+
+ } else {
+
+ log.error("doGet: Authenticator service missing, cannot login");
+
+ }
+
+ // fall back to forbid access
+ response.sendError(HttpServletResponse.SC_FORBIDDEN, "Cannot login");
+ }
+
+ private boolean isSelf(final String resourcePath) {
+ // no resource, assume self
+ if (resourcePath == null) {
+ return true;
+ }
+
+ // login servlet is addressed
+ if (resourcePath.startsWith(SERVLET_PATH)) {
+ return true;
+ }
+
+ // not a prefix
+ return false;
+ }
+}
diff --git a/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/impl/LogoutServlet.java b/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/impl/LogoutServlet.java
new file mode 100644
index 000000000..a2de3feae
--- /dev/null
+++ b/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/impl/LogoutServlet.java
@@ -0,0 +1,97 @@
+/*
+ * 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.auth.core.impl;
+
+import javax.servlet.Servlet;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.auth.Authenticator;
+import org.apache.sling.api.servlets.SlingAllMethodsServlet;
+import org.apache.sling.auth.core.AuthUtil;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicy;
+import org.osgi.service.component.propertytypes.ServiceDescription;
+import org.osgi.service.component.propertytypes.ServiceVendor;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The LogoutServlet lets the Authenticator
+ * do the logout.
+ */
+@Component(service = Servlet.class,
+ property = {
+ "sling.servlet.paths=" + LogoutServlet.SERVLET_PATH
+ })
+@ServiceDescription("Authenticator Logout Servlet")
+@ServiceVendor("The Apache Software Foundation")
+@Designate(ocd = LogoutServlet.Config.class)
+public class LogoutServlet extends SlingAllMethodsServlet {
+
+ @ObjectClassDefinition(name = "Apache Sling Authentication Logout Servlet",
+ description = "Servlet for logging out users through the authenticator service.")
+ public @interface Config {
+
+ @AttributeDefinition(name = "Method", description = "Supported Methods")
+ String[] sling_servlet_methods() default {"GET", "POST"};
+ }
+
+ /** serialization UID */
+ private static final long serialVersionUID = -1L;
+
+ /** default log */
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ @Reference(policy = ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.OPTIONAL)
+ private volatile Authenticator authenticator;
+
+ /**
+ * The servlet is registered on this path.
+ */
+ public static final String SERVLET_PATH = "/system/sling/logout";
+
+ @Override
+ protected void service(SlingHttpServletRequest request,
+ SlingHttpServletResponse response) {
+
+ final Authenticator authenticator = this.authenticator;
+ if (authenticator != null) {
+ try {
+ AuthUtil.setLoginResourceAttribute(request, null);
+ authenticator.logout(request, response);
+ return;
+ } catch (IllegalStateException ise) {
+ log.error("service: Response already committed, cannot logout");
+ return;
+ }
+ }
+
+ log.error("service: Authenticator service missing, cannot logout");
+
+ // well, we don't really have something to say here, do we ?
+ response.setStatus(HttpServletResponse.SC_NO_CONTENT);
+ }
+}
diff --git a/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/impl/PathBasedHolder.java b/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/impl/PathBasedHolder.java
new file mode 100644
index 000000000..2fc09eb90
--- /dev/null
+++ b/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/impl/PathBasedHolder.java
@@ -0,0 +1,223 @@
+/*
+ * 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.auth.core.impl;
+
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * The PathBasedHolder provides the basic abstraction for managing
+ * authentication handler and authentication requirements in the
+ * {@link SlingAuthenticator} with the following base functionality:
+ *
+ *
Provide location of control through its path fields
+ *
Support orderability of instances by being Comparable and
+ * ordering according to the {@link #fullPath} and the
+ * ServiceReference of the provider service
+ *
Support {@link #equals(Object)} and {@link #hashCode()} compatible with
+ * the Comparable implementation.
+ *
+ */
+public abstract class PathBasedHolder implements Comparable {
+
+ /**
+ * The full registration path of this instance. This is the actual URL with
+ * which this instance has been created.
+ */
+ protected final String fullPath;
+
+ /**
+ * The Scheme part of the URL of the {@link #fullPath}. If no scheme is
+ * contained, this field is set to an empty string.
+ */
+ final String protocol;
+
+ /**
+ * The host part of the URL of the {@link #fullPath}. If no host is
+ * contained, this field is set to an empty string.
+ */
+ final String host;
+
+ /**
+ * The path part of the URL of the {@link #fullPath}. If that URL contains
+ * neither a scheme nor a host, this field is actually set to the same as
+ * {@link #fullPath}.
+ */
+ final String path;
+
+ /**
+ * The ServiceReference to the service, which causes this
+ * instance to be created. This may be null if the entry has
+ * been created by the {@link SlingAuthenticator} itself.
+ */
+ final ServiceReference> serviceReference;
+
+ /**
+ * Sets up this instance with the given configuration URL provided by the
+ * given serviceReference.
+ *
+ * The serviceReference may be null which means
+ * the configuration is created by the {@link SlingAuthenticator} itself.
+ * Instances whose service reference is null are always ordered
+ * behind instances with non-null service references (provided
+ * their path is equal.
+ *
+ * @param url The configuration URL to setup this instance with
+ * @param serviceReference The reference to the service providing the
+ * configuration for this instance.
+ */
+ protected PathBasedHolder(final String url,
+ final ServiceReference> serviceReference) {
+
+ String path = url;
+ String host = "";
+ String protocol = "";
+
+ // check for protocol prefix in the full path
+ if (path.startsWith("http://") || path.startsWith("https://")) {
+ int idxProtocolEnd = path.indexOf("://");
+ protocol = path.substring(0, idxProtocolEnd);
+ path = path.substring(idxProtocolEnd + 1);
+ }
+
+ // check for host prefix in the full path
+ if (path.startsWith("//")) {
+ int idxHostEnd = path.indexOf("/", 2);
+ idxHostEnd = idxHostEnd == -1 ? path.length() : idxHostEnd;
+
+ if (path.length() > 2) {
+ host = path.substring(2, idxHostEnd);
+ if (idxHostEnd < path.length()) {
+ path = path.substring(idxHostEnd);
+ } else {
+ path = "/";
+ }
+ } else {
+ path = "/";
+ }
+ }
+
+ // assign the fields
+ this.fullPath = url;
+ this.path = path;
+ this.host = host;
+ this.protocol = protocol;
+ this.serviceReference = serviceReference;
+ }
+
+ /**
+ * Returns a descriptive string of the provider of this instance. The string
+ * is derived from the service reference with which this instance has been
+ * created. If the instance has been created without a service reference it
+ * is ordered the service description of the {@link SlingAuthenticator} is
+ * returned.
+ */
+ String getProvider() {
+ // assume the commons/auth SlingAuthenticator provides the entry
+ if (serviceReference == null) {
+ return "Apache Sling Request Authenticator";
+ }
+
+ final String descr = PropertiesUtil.toString(
+ serviceReference.getProperty(Constants.SERVICE_DESCRIPTION), null);
+ if (descr != null) {
+ return descr;
+ }
+
+ return "Service "
+ + PropertiesUtil.toString(
+ serviceReference.getProperty(Constants.SERVICE_ID), "unknown");
+ }
+
+ /**
+ * Compares this instance to the other PathBasedHolder
+ * instance. Comparison takes into account the {@link #path} first. If they
+ * are not equal the result is returned: If the other path is
+ * lexicographically sorted behind this {@link #path} a value larger than
+ * zero is returned; otherwise a value smaller than zero is returned.
+ *
+ * If the paths are the same, a positive number is returned if the
+ * other service reference is ordered after this service
+ * reference. If the service reference is the same, zero is returned.
+ *
+ * As a special case, zero is returned if other is the same
+ * object as this.
+ *
+ * If this service reference is null, -1 is always
+ * returned; if the other service reference is
+ * null, +1 is returned.
+ */
+ @Override
+ public int compareTo(PathBasedHolder other) {
+
+ // compare the path first, and return if not equal
+ final int pathResult = other.path.compareTo(path);
+ if (pathResult != 0) {
+ return pathResult;
+ }
+
+ // now compare the service references giving priority to
+ // to the higher priority service
+ if (serviceReference == null) {
+ if ( other.serviceReference == null ) {
+ return this.getClass().getName().compareTo(other.getClass().getName());
+ }
+ return -1;
+ } else if (other.serviceReference == null) {
+ return 1;
+ }
+
+ final int serviceResult = other.serviceReference.compareTo(serviceReference);
+ if ( serviceResult != 0 ) {
+ return serviceResult;
+ }
+ return this.getClass().getName().compareTo(other.getClass().getName());
+ }
+
+ /**
+ * Returns the hash code of the full path.
+ */
+ @Override
+ public int hashCode() {
+ return fullPath.hashCode();
+ }
+
+ /**
+ * Returns true if the other object is the same as this or if
+ * it is an instance of the same class with the same full path and the same
+ * provider (ServiceReference).
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ } else if (obj == null) {
+ return false;
+ }
+
+ if (obj.getClass() == getClass()) {
+ PathBasedHolder other = (PathBasedHolder) obj;
+ return fullPath.equals(other.fullPath)
+ && ((serviceReference == null && other.serviceReference == null) || (serviceReference != null && serviceReference.equals(other.serviceReference)));
+ }
+
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/impl/PathBasedHolderCache.java b/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/impl/PathBasedHolderCache.java
new file mode 100644
index 000000000..24dcf5aa8
--- /dev/null
+++ b/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/impl/PathBasedHolderCache.java
@@ -0,0 +1,143 @@
+/*
+ * 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.auth.core.impl;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import javax.servlet.http.HttpServletRequest;
+
+public class PathBasedHolderCache {
+
+ private final Map>> cache = new HashMap>>();
+
+ /** Read/write lock to synchronize the cache access. */
+ private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
+
+ public void clear() {
+ this.rwLock.writeLock().lock();
+ try {
+ cache.clear();
+ } finally {
+ this.rwLock.writeLock().unlock();
+ }
+ }
+
+ public void addHolder(final Type holder) {
+ this.rwLock.writeLock().lock();
+ try {
+
+ Map> byHostMap = cache.get(holder.protocol);
+ if (byHostMap == null) {
+ byHostMap = new HashMap>();
+ cache.put(holder.protocol, byHostMap);
+ }
+
+ final SortedSet byPathSet = new TreeSet();
+
+ // preset with current list
+ final SortedSet currentPathSet = byHostMap.get(holder.host);
+ if (currentPathSet != null) {
+ byPathSet.addAll(currentPathSet);
+ }
+
+ // add the new holder
+ byPathSet.add(holder);
+
+ // replace old set with new set
+ byHostMap.put(holder.host, byPathSet);
+ } finally {
+ this.rwLock.writeLock().unlock();
+ }
+ }
+
+ public void removeHolder(final Type holder) {
+ this.rwLock.writeLock().lock();
+ try {
+ final Map> byHostMap = cache.get(holder.protocol);
+ if (byHostMap != null) {
+ final SortedSet byPathSet = byHostMap.get(holder.host);
+ if (byPathSet != null) {
+
+ // create a new set without the removed holder
+ final SortedSet set = new TreeSet();
+ set.addAll(byPathSet);
+ set.remove(holder);
+
+ // replace the old set with the new one (or remove if empty)
+ if (set.isEmpty()) {
+ byHostMap.remove(holder.host);
+ } else {
+ byHostMap.put(holder.host, set);
+ }
+ }
+ }
+ } finally {
+ this.rwLock.writeLock().unlock();
+ }
+ }
+
+ public Collection[] findApplicableHolders(final HttpServletRequest request) {
+ this.rwLock.readLock().lock();
+ try {
+ final String hostname = request.getServerName()
+ + (request.getServerPort() != 80 && request.getServerPort() != 443
+ ? ":" + request.getServerPort()
+ : "");
+
+ @SuppressWarnings("unchecked")
+ final SortedSet[] result = new SortedSet[4];
+
+ final Map> byHostMap = cache.get(request.getScheme());
+ if ( byHostMap != null ) {
+ result[0] = byHostMap.get(hostname);
+ result[1] = byHostMap.get("");
+ }
+ final Map> defaultByHostMap = cache.get("");
+ if ( defaultByHostMap != null ) {
+ result[2] = defaultByHostMap.get(hostname);
+ result[3] = defaultByHostMap.get("");
+ }
+ return result;
+ } finally {
+ this.rwLock.readLock().unlock();
+ }
+ }
+
+ public List getHolders() {
+ this.rwLock.readLock().lock();
+ try {
+ final List result = new ArrayList();
+ for (Map> byHostEntry : cache.values()) {
+ for (SortedSet holderSet : byHostEntry.values()) {
+ result.addAll(holderSet);
+ }
+ }
+ return result;
+ } finally {
+ this.rwLock.readLock().unlock();
+ }
+ }
+}
diff --git a/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/impl/SlingAuthenticator.java b/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/impl/SlingAuthenticator.java
new file mode 100644
index 000000000..0ee22b38a
--- /dev/null
+++ b/Java-base/sling-org-apache-sling-auth-core/src/src/main/java/org/apache/sling/auth/core/impl/SlingAuthenticator.java
@@ -0,0 +1,1785 @@
+/*
+ * 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.auth.core.impl;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.SimpleCredentials;
+import javax.security.auth.login.AccountLockedException;
+import javax.security.auth.login.AccountNotFoundException;
+import javax.security.auth.login.CredentialExpiredException;
+import javax.servlet.Servlet;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletRequestEvent;
+import javax.servlet.ServletRequestListener;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.api.SlingConstants;
+import org.apache.sling.api.auth.Authenticator;
+import org.apache.sling.api.auth.NoAuthenticationHandlerException;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.auth.core.AuthConstants;
+import org.apache.sling.auth.core.AuthUtil;
+import org.apache.sling.auth.core.AuthenticationSupport;
+import org.apache.sling.auth.core.impl.engine.EngineAuthenticationHandlerHolder;
+import org.apache.sling.auth.core.spi.AbstractAuthenticationHandler;
+import org.apache.sling.auth.core.spi.AuthenticationFeedbackHandler;
+import org.apache.sling.auth.core.spi.AuthenticationHandler;
+import org.apache.sling.auth.core.spi.AuthenticationInfo;
+import org.apache.sling.auth.core.spi.AuthenticationInfoPostProcessor;
+import org.apache.sling.auth.core.spi.DefaultAuthenticationFeedbackHandler;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Modified;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicy;
+import org.osgi.service.component.propertytypes.ServiceDescription;
+import org.osgi.service.component.propertytypes.ServiceVendor;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+import org.osgi.service.http.context.ServletContextHelper;
+import org.osgi.service.http.whiteboard.HttpWhiteboardConstants;
+import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardContextSelect;
+import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardListener;
+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;
+import org.osgi.service.metatype.annotations.Option;
+import org.osgi.util.tracker.ServiceTracker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The SlingAuthenticator class is the default implementation for
+ * handling authentication. This class supports :
+ *
+ *
Support for login sessions where session ids are exchanged with cookies
+ *
Support for multiple authentication handlers, which must implement the
+ * {@link AuthenticationHandler} interface.
+ *
+ *
+ *
+ * Currently this class does not support multiple handlers for any one request
+ * URL.
+ */
+@Component(name = "org.apache.sling.engine.impl.auth.SlingAuthenticator",
+ service = {Authenticator.class, AuthenticationSupport.class, ServletRequestListener.class })
+@HttpWhiteboardContextSelect("(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME + "=*)")
+@HttpWhiteboardListener
+@ServiceDescription("Apache Sling Request Authenticator")
+@ServiceVendor("The Apache Software Foundation")
+@Designate(ocd = SlingAuthenticator.Config.class)
+public class SlingAuthenticator implements Authenticator,
+ AuthenticationSupport, ServletRequestListener {
+
+ @ObjectClassDefinition(name = "Apache Sling Authentication Service",
+ description = "Extracts user authentication details from the request with" +
+ " the help of authentication handlers registered as separate services. One" +
+ " example of such an authentication handler is the handler HTTP Authorization" +
+ " header contained authentication.")
+ public @interface Config {
+
+ @AttributeDefinition(name = "Impersonation Cookie",
+ description = "The name the HTTP Cookie to set with the value" +
+ " of the user which is to be impersonated. This cookie will always be a session" +
+ " cookie.")
+ String auth_sudo_cookie() default "sling.sudo";
+
+ @AttributeDefinition(name = "Impersonation Parameter",
+ description = "The name of the request parameter initiating" +
+ " impersonation. Setting this parameter to a user id will result in using an" +
+ " impersonated session (instead of the actually authenticated session) and set" +
+ " a session cookie of the name defined in the Impersonation Cookie setting.")
+ String auth_sudo_parameter() default "sudo";
+
+ @AttributeDefinition(name = "Allow Anonymous Access",
+ description = "Whether default access as anonymous when no" +
+ " credentials are present in the request is allowed. The default value is" +
+ " \"true\" to allow access without credentials. When set to \"false\" access to the" +
+ " repository is only allowed if valid credentials are presented. The value of" +
+ " this configuration option is added to list of Authentication Requirements" +
+ " and needs not be explicitly listed. If anonymous access is allowed the entry" +
+ " added is \"-/\". Otherwise anonymous access is denied and \"+/\" is added to the" +
+ " list.")
+ boolean auth_annonymous() default true;
+
+ @AttributeDefinition(name = "Authentication Requirements",
+ description = "Defines URL space subtrees which require" +
+ " or don't require authentication. For any request the best matching path" +
+ " configured applies and defines whether authentication is actually required" +
+ " for the request or not. Each entry in this list can be an absolute path (such" +
+ " as /content) or and absolute URI (such as http://thehost/content). Optionally" +
+ " each entry may be prefixed by a plus (+) or minus (-) sign indicating that" +
+ " authentication is required (plus) or not required (minus). Example entries are" +
+ " \"/content\" or \"+/content\" to require authentication at and below \"/content\" and" +
+ " \"-/system/sling/login\" to not require authentication at and below" +
+ " \"/system/sling/login\". By default this list is empty. This list is extended at" +
+ " run time with additional entries: One entry is added for the \"Allow Anonymous" +
+ " Access\" configuration. Other entries are added for any services setting the" +
+ " \"sling.auth.requirements\" service registration property.")
+ String[] sling_auth_requirements();
+
+ @AttributeDefinition(name = "Anonymous User Name",
+ description = "Defines which user name to assume" +
+ " for anonymous requests, that is requests not providing credentials" +
+ " supported by any of the registered authentication handlers. If this" +
+ " property is missing or empty, the default is assumed which depends on" +
+ " the resource provider(s). Otherwise anonymous requests are handled with" +
+ " this user name. If the configured user name does not exist or is not" +
+ " allowed to access the resource data, anonymous requests may still be" +
+ " blocked. If anonymous access is not allowed, this property is ignored.")
+ String sling_auth_anonymous_user();
+
+ @AttributeDefinition(name = "Anonymous User Password",
+ description = "Password for the anonymous" +
+ " user defined in the Anonymous User Name field. This property is only" +
+ " used if a non-empty anonymous user name is configured. If this property" +
+ " is not defined but a password is required, an empty password would be" +
+ " assumed.", type = AttributeType.PASSWORD)
+ String sling_auth_anonymous_password();
+
+ @AttributeDefinition(name = "HTTP Basic Authentication",
+ description = "Level of support for HTTP Basic Authentication. Such" +
+ " support can be provided in three levels: (1) no support at all, that is" +
+ " disabled, (2) preemptive support, that is HTTP Basic Authentication is" +
+ " supported if the authentication header is set in the request, (3) full" +
+ " support. The default is preemptive support unless Anonymous Access is" +
+ " not allowed. In this case HTTP Basic Authentication is always enabled" +
+ " to ensure clients can authenticate at least with basic authentication.",
+ options = {
+ @Option(label = "Enabled", value = HTTP_AUTH_ENABLED),
+ @Option(label = "Enabled (Preemptive)", value = HTTP_AUTH_PREEMPTIVE),
+ @Option(label = "Disabled", value = HTTP_AUTH_DISABLED)
+ })
+ String auth_http() default HTTP_AUTH_PREEMPTIVE;
+
+ @AttributeDefinition(name = "Realm",
+ description = "HTTP BASIC authentication realm. This property" +
+ " is only used if the HTTP Basic Authentication support is not disabled. The" +
+ " default value is \"Sling (Development)\".")
+ String auth_http_realm() default "Sling (Development)";
+
+ @AttributeDefinition(name = "Authentication URI Suffices",
+ description = "A list of request URI suffixes intended to" +
+ " be handled by Authentication Handlers. Any request whose request URI" +
+ " ends with any one of the listed suffices is intended to be handled by" +
+ " an Authentication Handler causing the request to either be rejected or" +
+ " the client being redirected to another location and thus the request not" +
+ " being further processed after the authentication phase. The default is" +
+ " just \"/j_security_check\" which is the suffix defined by the Servlet API" +
+ " specification used for FORM based authentication.")
+ String[] auth_uri_suffix() default DEFAULT_AUTH_URI_SUFFIX;
+ }
+
+ /** default log */
+ private final Logger log = LoggerFactory.getLogger(SlingAuthenticator.class);
+
+
+ /**
+ * Value of the {@link #PAR_HTTP_AUTH} property to fully enable the built-in
+ * HTTP Authentication Handler (value is "enabled").
+ */
+ private static final String HTTP_AUTH_ENABLED = "enabled";
+
+ /**
+ * Value of the {@link #PAR_HTTP_AUTH} property to completely disable the
+ * built-in HTTP Authentication Handler (value is "disabled").
+ */
+ private static final String HTTP_AUTH_DISABLED = "disabled";
+
+ /**
+ * Value of the {@link #PAR_HTTP_AUTH} property to enable extracting the
+ * credentials if the HTTP Basic authentication header is present (value is
+ * "preemptive"). In preemptive mode, though, the
+ * requestCredentials and dropCredentials methods
+ * will not send back a 401 response.
+ */
+ private static final String HTTP_AUTH_PREEMPTIVE = "preemptive";
+
+ /**
+ * Default request URI suffix to expect to be handled by authentication
+ * handlers and not expecting to cause
+ * {@link #handleSecurity(HttpServletRequest, HttpServletResponse)} to
+ * return true.
+ */
+ private static final String DEFAULT_AUTH_URI_SUFFIX = "/j_security_check";
+
+ /**
+ * The name of the form submission parameter providing the new password of
+ * the user (value is "j_newpassword").
+ */
+ private static final String PAR_NEW_PASSWORD = "j_newpassword";
+
+ /**
+ * The name of the {@link AuthenticationInfo} property providing the option
+ * {@link org.apache.sling.auth.core.spi.AuthenticationFeedbackHandler}
+ * handler to be called back on login failure or success.
+ */
+ private static final String AUTH_INFO_PROP_FEEDBACK_HANDLER = "$$sling.auth.AuthenticationFeedbackHandler$$";
+
+ @Reference
+ private ResourceResolverFactory resourceResolverFactory;
+
+ private PathBasedHolderCache authHandlerCache = new PathBasedHolderCache();
+
+ // package protected for access in inner class ...
+ private final PathBasedHolderCache authRequiredCache = new PathBasedHolderCache();
+
+ /** The name of the impersonation parameter */
+ private String sudoParameterName;
+
+ /** The name of the impersonation cookie */
+ private String sudoCookieName;
+
+ /** Cache control flag */
+ private boolean cacheControl;
+
+ /**
+ * The configured URI suffices indicating a authentication requests and
+ * requiring redirects and thus returning false from the
+ * #handleSecurity method.
+ *
+ * This will be null if there are no suffices to consider.
+ */
+ private String[] authUriSuffices;
+
+ /**
+ * The name of the user to assume for anonymous access. By default this is
+ * null to use null credentials and thus use the
+ * system provided identification.
+ *
+ * @see #getAnonymousCredentials()
+ */
+ private String anonUser;
+
+ /**
+ * The password to use for anonymous access. This property is only used if
+ * the {@link #anonUser} field is not null.
+ *
+ * @see #getAnonymousCredentials()
+ */
+ private char[] anonPassword;
+
+ /** HTTP Basic authentication handler */
+ private HttpBasicAuthenticationHandler httpBasicHandler;
+
+ /** Web Console Plugin service registration */
+ private ServiceRegistration webConsolePlugin;
+
+ /**
+ * The listener for services registered with "sling.auth.requirements" to
+ * update the internal authentication requirements
+ */
+ private SlingAuthenticatorServiceListener serviceListener;
+
+ /**
+ * ServiceTracker tracking AuthenticationHandler services
+ */
+ private ServiceTracker authHandlerTracker;
+
+ /**
+ * ServiceTracker tracking old Sling Engine AuthenticationHandler services
+ */
+ private ServiceTracker engineAuthHandlerTracker;
+
+ /**
+ * ServiceTracker tracking AuthenticationInfoPostProcessor services
+ */
+ private ServiceTracker authInfoPostProcessorTracker;
+
+ /**
+ * The event admin service.
+ */
+ @Reference(policy=ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.OPTIONAL)
+ private volatile EventAdmin eventAdmin;
+
+ // ---------- SCR integration
+
+ @Activate
+ private void activate(final BundleContext bundleContext,
+ final Config config) {
+ modified(config);
+
+ AuthenticatorWebConsolePlugin plugin = new AuthenticatorWebConsolePlugin(
+ this);
+ Hashtable props = new Hashtable();
+ props.put("felix.webconsole.label", plugin.getLabel());
+ props.put("felix.webconsole.title", plugin.getTitle());
+ props.put("felix.webconsole.category", "Sling");
+ props.put(Constants.SERVICE_DESCRIPTION,
+ "Sling Request Authenticator WebConsole Plugin");
+ props.put(Constants.SERVICE_VENDOR,
+ "The Apache Software Foundation");
+
+ webConsolePlugin = bundleContext.registerService(
+ Servlet.class, plugin, props);
+
+ serviceListener = SlingAuthenticatorServiceListener.createListener(
+ bundleContext, this.authRequiredCache);
+
+ authHandlerTracker = new AuthenticationHandlerTracker(bundleContext,
+ authHandlerCache);
+ engineAuthHandlerTracker = new EngineAuthenticationHandlerTracker(
+ bundleContext, authHandlerCache);
+ authInfoPostProcessorTracker = new ServiceTracker(bundleContext, AuthenticationInfoPostProcessor.class, null);
+ authInfoPostProcessorTracker.open();
+ }
+
+ @Modified
+ private void modified(Config config) {
+ String newCookie = config.auth_sudo_cookie();
+ if (!newCookie.equals(this.sudoCookieName)) {
+ log.info(
+ "modified: Setting new cookie name for impersonation {} (was {})",
+ newCookie, this.sudoCookieName);
+ this.sudoCookieName = newCookie;
+ }
+
+ String newPar = config.auth_sudo_parameter();
+ if (!newPar.equals(this.sudoParameterName)) {
+ log.info(
+ "modified: Setting new parameter name for impersonation {} (was {})",
+ newPar, this.sudoParameterName);
+ this.sudoParameterName = newPar;
+ }
+
+ authRequiredCache.clear();
+
+ final boolean anonAllowed = config.auth_annonymous();
+ authRequiredCache.addHolder(new AuthenticationRequirementHolder("/", !anonAllowed, null));
+
+ String[] authReqs = config.sling_auth_requirements();
+ if (authReqs != null) {
+ for (String authReq : authReqs) {
+ if (authReq != null && authReq.length() > 0) {
+ authRequiredCache.addHolder(AuthenticationRequirementHolder.fromConfig(
+ authReq, null));
+ }
+ }
+ }
+
+ final String anonUser = config.sling_auth_anonymous_user();
+ if (anonUser != null && anonUser.length() > 0) {
+ this.anonUser = anonUser;
+ this.anonPassword = config.sling_auth_anonymous_password() == null ? "".toCharArray() : config.sling_auth_anonymous_password().toCharArray();
+ } else {
+ this.anonUser = null;
+ this.anonPassword = null;
+ }
+
+ authUriSuffices = config.auth_uri_suffix();
+ // don't require authentication for login/logout servlets
+ authRequiredCache.addHolder(new AuthenticationRequirementHolder(
+ LoginServlet.SERVLET_PATH, false, null));
+ authRequiredCache.addHolder(new AuthenticationRequirementHolder(
+ LogoutServlet.SERVLET_PATH, false, null));
+
+ // add all registered services
+ if (serviceListener != null) {
+ serviceListener.registerAllServices();
+ }
+
+ final String http;
+ if (anonAllowed) {
+ http = config.auth_http();
+ } else {
+ http = HTTP_AUTH_ENABLED;
+ log.debug("modified: Anonymous Access is denied thus HTTP Basic Authentication is fully enabled");
+ }
+
+ if (HTTP_AUTH_DISABLED.equals(http)) {
+ httpBasicHandler = null;
+ } else {
+ final String realm = config.auth_http_realm();
+ httpBasicHandler = new HttpBasicAuthenticationHandler(realm, HTTP_AUTH_ENABLED.equals(http));
+ }
+ }
+
+ @Deactivate
+ private void deactivate(final BundleContext bundleContext) {
+ this.authRequiredCache.clear();
+ if (engineAuthHandlerTracker != null) {
+ engineAuthHandlerTracker.close();
+ engineAuthHandlerTracker = null;
+ }
+
+ if (authHandlerTracker != null) {
+ authHandlerTracker.close();
+ authHandlerTracker = null;
+ }
+
+ if (serviceListener != null) {
+ bundleContext.removeServiceListener(serviceListener);
+ serviceListener = null;
+ }
+
+ if (webConsolePlugin != null) {
+ webConsolePlugin.unregister();
+ webConsolePlugin = null;
+ }
+ }
+
+ // --------- AuthenticationSupport interface
+
+ /**
+ * Checks the authentication contained in the request. This check is only
+ * based on the original request object, no URI translation has taken place
+ * yet.
+ *
+ *
+ * @param request The request object containing the information for the
+ * authentication.
+ * @param response The response object which may be used to send the
+ * information on the request failure to the user.
+ * @return true if request processing should continue assuming
+ * successful authentication. If false is returned it
+ * is assumed a response has been sent to the client and the request
+ * is terminated.
+ */
+ @Override
+ public boolean handleSecurity(HttpServletRequest request,
+ HttpServletResponse response) {
+
+ // 0. Nothing to do, if the session is also in the request
+ // this might be the case if the request is handled as a result
+ // of a servlet container include inside another Sling request
+ Object sessionAttr = request.getAttribute(REQUEST_ATTRIBUTE_RESOLVER);
+ if (sessionAttr instanceof ResourceResolver) {
+ log.debug("handleSecurity: Request already authenticated, nothing to do");
+ return true;
+ } else if (sessionAttr != null) {
+ // warn and remove existing non-session
+ log.warn("handleSecurity: Overwriting existing ResourceResolver attribute ({})", sessionAttr);
+ request.removeAttribute(REQUEST_ATTRIBUTE_RESOLVER);
+ }
+
+ boolean process = doHandleSecurity(request, response);
+ if (process && expectAuthenticationHandler(request)) {
+ log.warn("handleSecurity: AuthenticationHandler did not block request; access denied");
+ request.removeAttribute(AuthenticationHandler.FAILURE_REASON);
+ request.removeAttribute(AuthenticationHandler.FAILURE_REASON_CODE);
+ AuthUtil.sendInvalid(request, response);
+ return false;
+ }
+
+ return process;
+ }
+
+ private boolean doHandleSecurity(HttpServletRequest request, HttpServletResponse response) {
+
+ // 0. Check for request attribute; set if not present
+ Object authUriSufficesAttr = request
+ .getAttribute(AuthConstants.ATTR_REQUEST_AUTH_URI_SUFFIX);
+ if (authUriSufficesAttr == null && authUriSuffices != null) {
+ request.setAttribute(AuthConstants.ATTR_REQUEST_AUTH_URI_SUFFIX,
+ authUriSuffices);
+ }
+
+ // 1. Ask all authentication handlers to try to extract credentials
+ final AuthenticationInfo authInfo = getAuthenticationInfo(request, response);
+
+ // 2. PostProcess credentials
+ try {
+ postProcess(authInfo, request, response);
+ } catch (LoginException e) {
+ postLoginFailedEvent(request, authInfo, e);
+
+ handleLoginFailure(request, response, authInfo, e);
+ return false;
+ }
+
+ // 3. Check Credentials
+ if (authInfo == AuthenticationInfo.DOING_AUTH) {
+
+ log.debug("doHandleSecurity: ongoing authentication in the handler");
+ return false;
+
+ } else if (authInfo == AuthenticationInfo.FAIL_AUTH) {
+
+ log.debug("doHandleSecurity: Credentials present but not valid, request authentication again");
+ AuthUtil.setLoginResourceAttribute(request, request.getRequestURI());
+ doLogin(request, response);
+ return false;
+
+ } else if (authInfo.getAuthType() == null) {
+
+ log.debug("doHandleSecurity: No credentials in the request, anonymous");
+ return getAnonymousResolver(request, response, authInfo);
+
+ } else {
+
+ log.debug("doHandleSecurity: Trying to get a session for {}", authInfo.getUser());
+ return getResolver(request, response, authInfo);
+
+ }
+ }
+
+ // ---------- Authenticator interface
+
+ /**
+ * Requests authentication information from the client. Returns
+ * true if the information has been requested and request
+ * processing can be terminated. Otherwise the request information could not
+ * be requested and the request should be terminated with a 403/FORBIDDEN
+ * response.
+ *
+ * Any response sent by the handler is also handled by the error handler
+ * infrastructure.
+ *
+ * @param request The request object
+ * @param response The response object to which to send the request
+ * @throws IllegalStateException If response is already committed
+ * @throws NoAuthenticationHandlerException If no authentication handler
+ * claims responsibility to authenticate the request.
+ */
+ @Override
+ public void login(HttpServletRequest request, HttpServletResponse response) {
+
+ // ensure the response is not committed yet
+ if (response.isCommitted()) {
+ throw new IllegalStateException("Response already committed");
+ }
+
+ // select path used for authentication handler selection
+ final Collection[] holdersArray = this.authHandlerCache
+ .findApplicableHolders(request);
+ final String path = getHandlerSelectionPath(request);
+ boolean done = false;
+ for (int m = 0; !done && m < holdersArray.length; m++) {
+ final Collection holderList = holdersArray[m];
+ if ( holderList != null ) {
+ for (AbstractAuthenticationHandlerHolder holder : holderList) {
+ if (isNodeRequiresAuthHandler(path, holder.path)) {
+ log.debug("login: requesting authentication using handler: {}",
+ holder);
+
+ try {
+ done = holder.requestCredentials(request, response);
+ } catch (IOException ioe) {
+ log.error(
+ "login: Failed sending authentication request through handler "
+ + holder + ", access forbidden", ioe);
+ done = true;
+ }
+ if (done) {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // fall back to HTTP Basic handler (if not done already)
+ if (!done && httpBasicHandler != null) {
+ done = httpBasicHandler.requestCredentials(request, response);
+ }
+
+ // no handler could send an authentication request, throw
+ if (!done) {
+ int size = 0;
+ for (int m = 0; m < holdersArray.length; m++) {
+ if (holdersArray[m] != null) {
+ size += holdersArray[m].size();
+ }
+ }
+ log.info("login: No handler for request ({} handlers available)", size);
+ throw new NoAuthenticationHandlerException();
+ }
+ }
+
+ /**
+ * Logs out the user calling all applicable
+ * {@link org.apache.sling.auth.core.spi.AuthenticationHandler}
+ * authentication handlers.
+ */
+ @Override
+ public void logout(HttpServletRequest request, HttpServletResponse response) {
+
+ // ensure the response is not committed yet
+ if (response.isCommitted()) {
+ throw new IllegalStateException("Response already committed");
+ }
+
+ // make sure impersonation is dropped
+ setSudoCookie(request, response, new AuthenticationInfo("dummy", request.getRemoteUser()));
+
+ final String path = getHandlerSelectionPath(request);
+ final Collection[] holdersArray = this.authHandlerCache
+ .findApplicableHolders(request);
+ for (int m = 0; m < holdersArray.length; m++) {
+ final Collection holderSet = holdersArray[m];
+ if (holderSet != null) {
+ for (AbstractAuthenticationHandlerHolder holder : holderSet) {
+ if (isNodeRequiresAuthHandler(path, holder.path)) {
+ log.debug("logout: dropping authentication using handler: {}",
+ holder);
+
+ try {
+ holder.dropCredentials(request, response);
+ } catch (IOException ioe) {
+ log.error(
+ "logout: Failed dropping authentication through handler "
+ + holder, ioe);
+ }
+ }
+ }
+ }
+ }
+
+ if (httpBasicHandler != null) {
+ httpBasicHandler.dropCredentials(request, response);
+ }
+
+ redirectAfterLogout(request, response);
+ }
+
+ // ---------- ServletRequestListener
+
+ @Override
+ public void requestInitialized(ServletRequestEvent sre) {
+ // don't care
+ }
+
+ @Override
+ public void requestDestroyed(ServletRequestEvent sre) {
+ ServletRequest request = sre.getServletRequest();
+ Object resolverAttr = request.getAttribute(REQUEST_ATTRIBUTE_RESOLVER);
+ if (resolverAttr instanceof ResourceResolver) {
+ ((ResourceResolver) resolverAttr).close();
+ request.removeAttribute(REQUEST_ATTRIBUTE_RESOLVER);
+ }
+ }
+
+ // ---------- WebConsolePlugin support
+
+ /**
+ * Returns the list of registered authentication handlers as a map
+ */
+ Map> getAuthenticationHandler() {
+ List registeredHolders = authHandlerCache.getHolders();
+ LinkedHashMap> handlerMap = new LinkedHashMap>();
+ for (AbstractAuthenticationHandlerHolder holder : registeredHolders) {
+ List provider = handlerMap.get(holder.fullPath);
+ if (provider == null) {
+ provider = new ArrayList();
+ handlerMap.put(holder.fullPath, provider);
+ }
+ provider.add(holder.getProvider());
+ }
+ if (httpBasicHandler != null) {
+ List provider = handlerMap.get("/");
+ if (provider == null) {
+ provider = new ArrayList();
+ handlerMap.put("/", provider);
+ }
+ provider.add(httpBasicHandler.toString());
+ }
+ return handlerMap;
+ }
+
+ List getAuthenticationRequirements() {
+ return authRequiredCache.getHolders();
+ }
+
+ /**
+ * Returns the name of the user to assume for requests without credentials.
+ * This may be null if not configured and the default anonymous
+ * user is to be used.
+ *
+ * The configured password cannot be requested.
+ */
+ String getAnonUserName() {
+ return anonUser;
+ }
+
+ String getSudoCookieName() {
+ return sudoCookieName;
+ }
+
+ String getSudoParameterName() {
+ return sudoParameterName;
+ }
+
+ // ---------- internal
+
+ private String getPath(HttpServletRequest request) {
+ final StringBuilder sb = new StringBuilder();
+ if (request.getServletPath() != null) {
+ sb.append(request.getServletPath());
+ }
+ if (request.getPathInfo() != null) {
+ sb.append(request.getPathInfo());
+ }
+ return sb.toString();
+ }
+
+ private AuthenticationInfo getAuthenticationInfo(HttpServletRequest request, HttpServletResponse response) {
+
+ // Get the path used to select the authenticator, if the SlingServlet
+ // itself has been requested without any more info, this will be empty
+ // and we assume the root (SLING-722)
+ String path = getPath(request);
+ if (path.length() == 0) {
+ path = "/";
+ }
+
+ final Collection[] localArray = this.authHandlerCache
+ .findApplicableHolders(request);
+ for (int m = 0; m < localArray.length; m++) {
+ final Collection local = localArray[m];
+ if (local != null) {
+ for (AbstractAuthenticationHandlerHolder holder : local) {
+ if (isNodeRequiresAuthHandler(path, holder.path)){
+ final AuthenticationInfo authInfo = holder.extractCredentials(
+ request, response);
+
+ if (authInfo != null) {
+ // add the feedback handler to the info (may be null)
+ authInfo.put(AUTH_INFO_PROP_FEEDBACK_HANDLER,
+ holder.getFeedbackHandler());
+
+ return authInfo;
+ }
+ }
+ }
+ }
+ }
+
+ // check whether the HTTP Basic handler can extract the header
+ if (httpBasicHandler != null) {
+ final AuthenticationInfo authInfo = httpBasicHandler.extractCredentials(
+ request, response);
+ if (authInfo != null) {
+ authInfo.put(AUTH_INFO_PROP_FEEDBACK_HANDLER, httpBasicHandler);
+ return authInfo;
+ }
+ }
+
+ // no handler found for the request ....
+ log.debug("getAuthenticationInfo: no handler could extract credentials; assuming anonymous");
+ return getAnonymousCredentials();
+ }
+
+ /**
+ * Run through the available post processors.
+ */
+ private void postProcess(AuthenticationInfo info, HttpServletRequest request, HttpServletResponse response)
+ throws LoginException {
+ Object[] services = authInfoPostProcessorTracker.getServices();
+ if (services != null) {
+ for (Object serviceObj : services) {
+ ((AuthenticationInfoPostProcessor)serviceObj).postProcess(info, request, response);
+ }
+ }
+ }
+
+
+ /**
+ * Try to acquire a ResourceResolver as indicated by authInfo
+ *
+ * @return true if request processing should continue assuming
+ * successful authentication. If false is returned it
+ * is assumed a response has been sent to the client and the request
+ * is terminated.
+ */
+ private boolean getResolver(final HttpServletRequest request,
+ final HttpServletResponse response,
+ final AuthenticationInfo authInfo) {
+
+ // prepare the feedback handler
+ final AuthenticationFeedbackHandler feedbackHandler = (AuthenticationFeedbackHandler) authInfo.remove(AUTH_INFO_PROP_FEEDBACK_HANDLER);
+ final Object sendLoginEvent = authInfo.remove(AuthConstants.AUTH_INFO_LOGIN);
+
+ // try to connect
+ try {
+ handleImpersonation(request, authInfo);
+ handlePasswordChange(request, authInfo);
+ ResourceResolver resolver = resourceResolverFactory.getResourceResolver(authInfo);
+ final boolean impersChanged = setSudoCookie(request, response, authInfo);
+
+ if (sendLoginEvent != null) {
+ postLoginEvent(authInfo);
+ }
+
+ // provide the resource resolver to the feedback handler
+ request.setAttribute(REQUEST_ATTRIBUTE_RESOLVER, resolver);
+
+ boolean processRequest = true;
+
+ // custom feedback handler with option to redirect
+ if (feedbackHandler != null) {
+ processRequest = !feedbackHandler.authenticationSucceeded(request, response, authInfo);
+ }
+
+ if (processRequest) {
+ if (AuthUtil.isValidateRequest(request)) {
+ AuthUtil.sendValid(response);
+ processRequest = false;
+ } else if (impersChanged || feedbackHandler == null) {
+ processRequest = !DefaultAuthenticationFeedbackHandler.handleRedirect(request, response);
+ }
+ }
+
+ if (processRequest) {
+ // process: set required attributes
+ setAttributes(resolver, authInfo.getAuthType(), request);
+ } else {
+ // terminate: cleanup
+ resolver.close();
+ }
+
+ return processRequest;
+
+ } catch (LoginException re) {
+ postLoginFailedEvent(request, authInfo, re);
+
+ // handle failure feedback before proceeding to handling the
+ // failed login internally
+ if (feedbackHandler != null) {
+ feedbackHandler.authenticationFailed(request, response,
+ authInfo);
+ }
+
+ // now find a way to get credentials unless the feedback handler
+ // has committed a response to the client already
+ if (!response.isCommitted()) {
+ return handleLoginFailure(request, response, authInfo, re);
+ }
+
+ }
+
+ // end request
+ return false;
+
+ }
+
+ private boolean expectAuthenticationHandler(final HttpServletRequest request) {
+ if (this.authUriSuffices != null) {
+ final String requestUri = request.getRequestURI();
+ for (final String uriSuffix : this.authUriSuffices) {
+ if (requestUri.endsWith(uriSuffix)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /** Try to acquire an anonymous ResourceResolver */
+ private boolean getAnonymousResolver(final HttpServletRequest request,
+ final HttpServletResponse response, final AuthenticationInfo authInfo) {
+
+ // Get an anonymous session if allowed, or if we are handling
+ // a request for the login servlet
+ if (isAnonAllowed(request)) {
+
+ try {
+ ResourceResolver resolver = resourceResolverFactory.getResourceResolver(authInfo);
+
+ // check whether the client asked for redirect after
+ // authentication and/or impersonation
+ if (DefaultAuthenticationFeedbackHandler.handleRedirect(
+ request, response)) {
+
+ // request will now be terminated, so close the resolver
+ // to release resources
+ resolver.close();
+
+ return false;
+ }
+
+ // set the attributes for further processing
+ setAttributes(resolver, null, request);
+
+ return true;
+
+ } catch (LoginException re) {
+
+ // cannot login > fail login, do not try to authenticate
+ handleLoginFailure(request, response, new AuthenticationInfo(null, "anonymous user"), re);
+ return false;
+
+ }
+ }
+
+ // If we get here, anonymous access is not allowed: redirect
+ // to the login servlet
+ log.info("getAnonymousResolver: Anonymous access not allowed by configuration - requesting credentials");
+ doLogin(request, response);
+
+ // fallback to no session
+ return false;
+ }
+
+ private boolean isAnonAllowed(HttpServletRequest request) {
+
+ String path = getPath(request);
+ if (path.length() == 0) {
+ path = "/";
+ }
+
+ final Collection[] holderSetArray = authRequiredCache
+ .findApplicableHolders(request);
+ for (int m = 0; m < holderSetArray.length; m++) {
+ final Collection holders = holderSetArray[m];
+ if (holders != null) {
+ for (AuthenticationRequirementHolder holder : holders) {
+ if (isNodeRequiresAuthHandler(path, holder.path)) {
+ return !holder.requiresAuthentication();
+ }
+ }
+ }
+ }
+
+ // fallback to anonymous not allowed (aka authentication required)
+ return false;
+ }
+
+ private boolean isNodeRequiresAuthHandler(String path, String holderPath) {
+ if (path == null || holderPath == null) {
+ return false;
+ }
+
+ if (("/").equals(holderPath)) {
+ return true;
+ }
+
+ int holderPathLength = holderPath.length();
+
+ if (path.length() < holderPathLength) {
+ return false;
+ }
+
+ if (path.equals(holderPath)) {
+ return true;
+ }
+
+ if (path.startsWith(holderPath)) {
+ if (path.charAt(holderPathLength) == '/' || path.charAt(holderPathLength) == '.') {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ /**
+ * Returns credentials to use for anonymous resource access. If an anonymous
+ * user is configued, this returns an {@link AuthenticationInfo} instance
+ * whose authentication type is null and the user name and
+ * password are set according to the {@link #PAR_ANONYMOUS_USER} and
+ * {@link #PAR_ANONYMOUS_PASSWORD} configurations. Otherwise
+ * the user name and password fields are just null.
+ */
+ private AuthenticationInfo getAnonymousCredentials() {
+ AuthenticationInfo info = new AuthenticationInfo(null);
+ if (this.anonUser != null) {
+ info.setUser(this.anonUser);
+ info.setPassword(this.anonPassword);
+ }
+ return info;
+ }
+
+ private boolean handleLoginFailure(final HttpServletRequest request,
+ final HttpServletResponse response, final AuthenticationInfo authInfo,
+ final Exception reason) {
+
+ String user = authInfo.getUser();
+ boolean processRequest = false;
+ if (reason.getClass().getName().contains("TooManySessionsException")) {
+
+ // to many users, send a 503 Service Unavailable
+ log.info("handleLoginFailure: Too many sessions for {}: {}", user,
+ reason.getMessage());
+
+ try {
+ response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
+ "SlingAuthenticator: Too Many Users");
+ } catch (IOException ioe) {
+ log.error(
+ "handleLoginFailure: Cannot send status 503 to client", ioe);
+ }
+
+ } else if (reason instanceof LoginException) {
+ log.info("handleLoginFailure: Unable to authenticate {}: {}", user,
+ reason.getMessage());
+ if (isAnonAllowed(request) && !expectAuthenticationHandler(request) && !AuthUtil.isValidateRequest(request)) {
+ log.debug("handleLoginFailure: LoginException on an anonymous resource, fallback to getAnonymousResolver");
+ processRequest = getAnonymousResolver(request, response, new AuthenticationInfo(null));
+ } else {
+ // request authentication information and send 403 (Forbidden)
+ // if no handler can request authentication information.
+
+ AuthenticationHandler.FAILURE_REASON_CODES code = getFailureReasonFromException(authInfo, reason);
+ String message = null;
+ switch (code) {
+ case ACCOUNT_LOCKED:
+ message = "Account is locked";
+ break;
+ case ACCOUNT_NOT_FOUND:
+ message = "Account was not found";
+ break;
+ case PASSWORD_EXPIRED:
+ message = "Password expired";
+ break;
+ case PASSWORD_EXPIRED_AND_NEW_PASSWORD_IN_HISTORY:
+ message = "Password expired and new password found in password history";
+ break;
+ case UNKNOWN:
+ case INVALID_LOGIN:
+ default:
+ message = "User name and password do not match";
+ break;
+ }
+
+ // preset a reason for the login failure
+ request.setAttribute(AuthenticationHandler.FAILURE_REASON_CODE, code);
+ ensureAttribute(request, AuthenticationHandler.FAILURE_REASON, message);
+
+ doLogin(request, response);
+ }
+
+ } else {
+
+ // general problem, send a 500 Internal Server Error
+ log.error("handleLoginFailure: Unable to authenticate " + user,
+ reason);
+
+ try {
+ response.sendError(
+ HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+ "SlingAuthenticator: data access error, reason="
+ + reason.getClass().getSimpleName());
+ } catch (IOException ioe) {
+ log.error(
+ "handleLoginFailure: Cannot send status 500 to client", ioe);
+ }
+ }
+ return processRequest;
+
+ }
+
+ /**
+ * Try to determine the failure reason from the thrown exception
+ */
+ private AuthenticationHandler.FAILURE_REASON_CODES getFailureReasonFromException(final AuthenticationInfo authInfo, Exception reason) {
+ AuthenticationHandler.FAILURE_REASON_CODES code = null;
+ if (reason.getClass().getName().contains("TooManySessionsException")) {
+ // not a login failure just unavailable service
+ code = null;
+ } else if (reason instanceof LoginException) {
+ if (reason.getCause() instanceof CredentialExpiredException) {
+ // force failure attribute to be set so handlers can
+ // react to this special circumstance
+ Object creds = authInfo.get("user.jcr.credentials");
+ if (creds instanceof SimpleCredentials && ((SimpleCredentials) creds).getAttribute("PasswordHistoryException") != null) {
+ code = AuthenticationHandler.FAILURE_REASON_CODES.PASSWORD_EXPIRED_AND_NEW_PASSWORD_IN_HISTORY;
+ } else {
+ code = AuthenticationHandler.FAILURE_REASON_CODES.PASSWORD_EXPIRED;
+ }
+ } else if (reason.getCause() instanceof AccountLockedException) {
+ code = AuthenticationHandler.FAILURE_REASON_CODES.ACCOUNT_LOCKED;
+ } else if (reason.getCause() instanceof AccountNotFoundException) {
+ code = AuthenticationHandler.FAILURE_REASON_CODES.ACCOUNT_NOT_FOUND;
+ }
+
+ if (code == null) {
+ // default to invalid login as the reason
+ code = AuthenticationHandler.FAILURE_REASON_CODES.INVALID_LOGIN;
+ }
+ }
+
+ return code;
+ }
+
+ /**
+ * Tries to request credentials from the client. The following mechanisms
+ * are implemented by this method:
+ *
+ *
If the request is a credentials validation request (see
+ * {@link AbstractAuthenticationHandler#isValidateRequest(HttpServletRequest)}
+ * ) a 403/FORBIDDEN response is sent back.
+ *
If the request is not considered a
+ * {@link #isBrowserRequest(HttpServletRequest) browser request} and the
+ * HTTP Basic Authentication Handler is at least enabled for preemptive
+ * credentials processing, a 401/UNAUTHORIZED response is sent back. This
+ * helps implementing HTTP Basic authentication with WebDAV clients. If HTTP
+ * Basic Authentication is completely switched of a 403/FORBIDDEN response
+ * is sent back instead.
+ *
If the request is considered an
+ * {@link #isAjaxRequest(HttpServletRequest) Ajax request} a 403/FORBIDDEN
+ * response is simply sent back because we assume an Ajax requestor cannot
+ * properly handle any request for credentials graciously.
+ *
Otherwise the {@link #login(HttpServletRequest, HttpServletResponse)}
+ * method is called to try to find and call an authentication handler to
+ * request credentials from the client. If none is available or willing to
+ * request credentials, a 403/FORBIDDEN response is also sent back to the
+ * client.
+ *
+ *
+ * If a 403/FORBIDDEN response is sent back the {@link AbstractAuthenticationHandler#X_REASON} header is
+ * set to a either the value of the
+ * {@link AuthenticationHandler#FAILURE_REASON} request attribute or to some
+ * generic description describing the reason. To actually send the response
+ * the
+ * {@link AbstractAuthenticationHandler#sendInvalid(HttpServletRequest, HttpServletResponse)}
+ * method is called.
+ *
+ * This method is called in three situations:
+ *
+ *
If the request contains no credentials but anonymous login is not
+ * allowed
+ *
If the request contains credentials but getting the Resource Resolver
+ * using the provided credentials fails
+ *
If the selected authentication handler indicated any presented
+ * credentials are not valid
+ *
+ *
+ * @param request The current request
+ * @param response The response to send the credentials request (or access
+ * denial to)
+ * @see AbstractAuthenticationHandler#isValidateRequest(HttpServletRequest)
+ * @see #isBrowserRequest(HttpServletRequest)
+ * @see #isAjaxRequest(HttpServletRequest)
+ * @see AbstractAuthenticationHandler#sendInvalid(HttpServletRequest,
+ * HttpServletResponse)
+ */
+ private void doLogin(HttpServletRequest request,
+ HttpServletResponse response) {
+
+ if (!AuthUtil.isValidateRequest(request)) {
+
+ if (AuthUtil.isBrowserRequest(request)) {
+
+ if (!AuthUtil.isAjaxRequest(request) && !isLoginLoop(request)) {
+ try {
+
+ login(request, response);
+ return;
+
+ } catch (IllegalStateException ise) {
+
+ log.error("doLogin: Cannot login: Response already committed");
+ return;
+
+ } catch (NoAuthenticationHandlerException nahe) {
+
+ /*
+ * Don't set the failureReason for missing
+ * authentication handlers to not disclose this setup
+ * information.
+ */
+
+ log.error("doLogin: Cannot login: No AuthenticationHandler available to handle the request");
+
+ }
+ }
+
+ } else {
+
+ // Presumably this is WebDAV. If HTTP Basic is fully enabled or
+ // enabled for preemptive credential support, we just request
+ // HTTP Basic credentials. Otherwise (HTTP Basic is fully
+ // switched off, 403 is sent back)
+ if (httpBasicHandler != null) {
+ httpBasicHandler.sendUnauthorized(response);
+ return;
+ }
+
+ }
+ }
+
+ // if we are here, we cannot redirect to the login form because it is
+ // an XHR request or because there is no authentication handler willing
+ // request credentials from the client or because it is a failed
+ // credential validation
+
+ // ensure a failure reason
+ ensureAttribute(request, AuthenticationHandler.FAILURE_REASON,
+ "Authentication Failed");
+
+ AuthUtil.sendInvalid(request, response);
+ }
+
+ /**
+ * Returns true if the current request was referred to by the
+ * same URL as the current request has. This is assumed to be caused by a
+ * loop in requesting credentials from the client. Such a loop will probably
+ * never cause the request for credentials to succeed, so it must be broken.
+ *
+ * @param request The request to check
+ * @return true if the request is considered to be a loop;
+ * false otherwise
+ */
+ private boolean isLoginLoop(final HttpServletRequest request) {
+ String referer = request.getHeader("Referer");
+ if (referer != null) {
+ StringBuffer sb = request.getRequestURL();
+ if (request.getQueryString() != null) {
+ sb.append('?').append(request.getQueryString());
+ }
+ return referer.equals(sb.toString());
+ }
+
+ // no referer means no loop
+ return false;
+ }
+
+ /**
+ * Sets the name request attribute to the given value unless the request
+ * attribute is already set a non-null value.
+ *
+ * @param request The request on which to set the attribute
+ * @param attribute The name of the attribute to check/set
+ * @param value The value to set the attribute to if it is not already set
+ */
+ private void ensureAttribute(final HttpServletRequest request,
+ final String attribute, final Object value) {
+ if (request.getAttribute(attribute) == null) {
+ request.setAttribute(attribute, value);
+ }
+ }
+
+ /**
+ * Sets the request attributes required by the OSGi HttpContext interface
+ * specification for the handleSecurity method. In addition the
+ * {@link SlingAuthenticator#REQUEST_ATTRIBUTE_RESOLVER} request attribute
+ * is set to the ResourceResolver.
+ */
+ private void setAttributes(final ResourceResolver resolver, final String authType,
+ final HttpServletRequest request) {
+
+ // HttpService API required attributes
+ request.setAttribute(ServletContextHelper.REMOTE_USER, resolver.getUserID());
+ request.setAttribute(ServletContextHelper.AUTHENTICATION_TYPE, authType);
+
+ // resource resolver for down-stream use
+ request.setAttribute(REQUEST_ATTRIBUTE_RESOLVER, resolver);
+
+ log.debug(
+ "setAttributes: ResourceResolver stored as request attribute: user={}",
+ resolver.getUserID());
+ }
+
+ /**
+ * Sends the session cookie for the name session with the given age in
+ * seconds. This sends a Version 1 cookie.
+ *
+ * @param response The {@link HttpServletResponse} on which to send
+ * back the cookie.
+ * @param user The name of the user to impersonate as. This will be quoted
+ * and used as the cookie value.
+ * @param maxAge The maximum age of the cookie in seconds. Positive values
+ * are persisted on the browser for the indicated number of
+ * seconds, setting the age to 0 (zero) causes the cookie to be
+ * deleted in the browser and using a negative value defines a
+ * temporary cookie to be deleted when the browser exits.
+ * @param path The cookie path to use. This is intended to be the web
+ * application's context path. If this is empty or
+ * null the root path will be used assuming the web
+ * application is registered at the root context.
+ * @param owner The name of the user originally authenticated in the request
+ * and who is now impersonating as user.
+ */
+ private void sendSudoCookie(HttpServletResponse response,
+ final String user, final int maxAge, final String path,
+ final String owner) {
+
+ final String quotedUser;
+ String quotedOwner = null;
+ try {
+ quotedUser = quoteCookieValue(user);
+ if (owner != null) {
+ quotedOwner = quoteCookieValue(owner);
+ }
+ } catch (IllegalArgumentException iae) {
+ log.error(
+ "sendSudoCookie: Failed to quote value '{}' of cookie {}: {}",
+ new Object[] { user, this.sudoCookieName, iae.getMessage() });
+ return;
+ } catch (UnsupportedEncodingException e) {
+ log.error(
+ "sendSudoCookie: Failed to quote value '{}' of cookie {}: {}",
+ new Object[] { user, this.sudoCookieName, e.getMessage() });
+ return;
+ }
+
+ if (quotedUser != null) {
+ Cookie cookie = new Cookie(this.sudoCookieName, quotedUser);
+ cookie.setMaxAge(maxAge);
+ cookie.setPath((path == null || path.length() == 0) ? "/" : path);
+ try {
+ cookie.setComment(quotedOwner + " impersonates as " +quotedUser);
+ } catch (IllegalArgumentException iae) {
+ // ignore
+ }
+
+ response.addCookie(cookie);
+
+ // Tell a potential proxy server that this request is uncacheable
+ // due to the Set-Cookie header being sent
+ if (this.cacheControl) {
+ response.addHeader("Cache-Control", "no-cache=\"Set-Cookie\"");
+ }
+ }
+ }
+
+ /**
+ * Handles impersonation based on the request parameter for impersonation
+ * (see {@link #sudoParameterName}) and the current setting in the sudo
+ * cookie.
+ *
+ * If the sudo parameter is empty or missing, the current cookie setting for
+ * impersonation is used. Else if the parameter is -, the
+ * current cookie impersonation is removed and no impersonation will take
+ * place for this request. Else the parameter is assumed to contain the name
+ * of a user to impersonate as.
+ *
+ * @param req The {@link HttpServletRequest} optionally containing
+ * the sudo parameter.
+ * @param authInfo The authentication info into which the
+ * sudo.user.id property is set to the impersonator
+ * user.
+ */
+ private void handleImpersonation(HttpServletRequest req,
+ AuthenticationInfo authInfo) {
+ String currentSudo = getSudoCookieValue(req);
+
+ /**
+ * sudo parameter : empty or missing to continue to use the setting
+ * already stored in the session; or "-" to remove impersonation
+ * altogether (also from the session); or the handle of a user page to
+ * impersonate as that user (if possible)
+ */
+ String sudo = req.getParameter(this.sudoParameterName);
+ if (sudo == null || sudo.length() == 0) {
+ sudo = currentSudo;
+ } else if ("-".equals(sudo)) {
+ sudo = null;
+ }
+
+ // sudo the session if needed
+ if (sudo != null && sudo.length() > 0) {
+ authInfo.put(ResourceResolverFactory.USER_IMPERSONATION, sudo);
+ }
+ }
+
+ /**
+ * Handles password change based on the request parameter for the new password
+ * (see {@link #newPasswordParameterName}).
+ *
+ * If the new password request parameter is present, it is added to the authInfo
+ * object, which is later transformed to SimpleCredentials attributes.
+ *
+ * @param req The {@link HttpServletRequest} optionally containing
+ * the new password parameter.
+ * @param authInfo The authentication info into which the
+ * newPassword property is set.
+ */
+ private void handlePasswordChange(HttpServletRequest req, AuthenticationInfo authInfo) {
+ String newPassword = req.getParameter(PAR_NEW_PASSWORD );
+ if (newPassword != null && newPassword.length() > 0) {
+ authInfo.put("user.newpassword", newPassword);
+ }
+ }
+
+ private String getSudoCookieValue(HttpServletRequest req) {
+ // the current state of impersonation
+ String currentSudo = null;
+ Cookie[] cookies = req.getCookies();
+ if (cookies != null) {
+ for (int i = 0; currentSudo == null && i < cookies.length; i++) {
+ if (sudoCookieName.equals(cookies[i].getName())) {
+ currentSudo = unquoteCookieValue(cookies[i].getValue());
+ }
+ }
+ }
+ return currentSudo;
+ }
+
+ /**
+ * Sets the impersonation cookie on the response if impersonation actually
+ * changed and returns whether the cookie has been set (or cleared) or not.
+ *
+ * The current impersonation state is taken from the sudo cookie value
+ * while the desired state is taken from the user.impersonation
+ * property of the auth info. If they don't match, the sudo cookie
+ * is set according to the user.impersonation property where the
+ * cookie is actually cleared if the property is null.
+ *
+ * @param req Providing the current sudo cookie value
+ * @param res For setting the sudo cookie
+ * @param authInfo Providing information about desired impersonation
+ * @return true if the cookie has been set or cleared or
+ * false if the cookie is not modified.
+ */
+ private boolean setSudoCookie(HttpServletRequest req,
+ HttpServletResponse res, AuthenticationInfo authInfo) {
+ String sudo = (String) authInfo.get(ResourceResolverFactory.USER_IMPERSONATION);
+ String currentSudo = getSudoCookieValue(req);
+
+ // set the (new) impersonation
+ final boolean setCookie = sudo != currentSudo;
+ if (setCookie) {
+ if (sudo == null) {
+ // Parameter set to "-" to clear impersonation, which was
+ // active due to cookie setting
+
+ // clear impersonation
+ this.sendSudoCookie(res, "", 0, req.getContextPath(), authInfo.getUser());
+
+ } else if (currentSudo == null || !currentSudo.equals(sudo)) {
+ // Parameter set to a name. As the cookie is not set yet
+ // or is set to another name, send the cookie with current sudo
+
+ // (re-)set impersonation
+ this.sendSudoCookie(res, sudo, -1, req.getContextPath(),
+ sudo);
+ }
+ }
+
+ return setCookie;
+ }
+
+ /**
+ * Returns the path to be used to select the authentication handler to login
+ * or logout with.
+ *
+ * This method uses the {@link Authenticator#LOGIN_RESOURCE} request
+ * attribute. If this attribute is not set (or is not a string), the request
+ * path info is used. If this is not set either, or is the empty string, "/"
+ * is returned.
+ *
+ * @param request The request providing the request attribute or path info.
+ * @return The path as set by the request attribute or the path info or "/"
+ * if neither is set.
+ */
+ private String getHandlerSelectionPath(HttpServletRequest request) {
+ final Object loginPathO = request.getAttribute(Authenticator.LOGIN_RESOURCE);
+ String path;
+ if (loginPathO instanceof String) {
+ path = (String) loginPathO;
+ final String ctxPath = request.getContextPath();
+ if (ctxPath != null && path.startsWith(ctxPath)) {
+ path = path.substring(ctxPath.length());
+ }
+ } else {
+ path = getPath(request);
+ }
+
+ if (path == null || path.length() == 0) {
+ path = "/";
+ }
+
+ return path;
+ }
+
+ /**
+ * If the response has not been committed yet, redirect to target requested
+ * by the resource request attribute or parameter. If neither
+ * is set to a non-null string, the request is redirected to the context
+ * root.
+ *
+ * The response is not reset though, since the handler may have set states
+ * such as an updated HTTP session or some Cookie
+ *
+ * @param request The request providing the redirect target
+ * @param response The response to send the redirect to
+ */
+ private void redirectAfterLogout(final HttpServletRequest request,
+ final HttpServletResponse response) {
+
+ // nothing more to do if the response has already been committed
+ if (response.isCommitted()) {
+ log.debug("redirectAfterLogout: Response has already been committed, not redirecting");
+ return;
+ }
+
+ // find the redirect target from the resource attribute or parameter
+ // falling back to the request context path (or /) if not set or invalid
+ String target = AuthUtil.getLoginResource(request, request.getContextPath());
+ if (!AuthUtil.isRedirectValid(request, target)) {
+ log.warn("redirectAfterLogout: Desired redirect target '{}' is invalid; redirecting to '/'", target);
+ target = request.getContextPath() + "/";
+ }
+
+ // redirect to there
+ try {
+ response.sendRedirect(target);
+ } catch (IOException e) {
+ log.error("Failed to redirect to the page: " + target, e);
+ }
+ }
+
+ private void postLoginEvent(final AuthenticationInfo authInfo) {
+ final Dictionary properties = new Hashtable();
+ properties.put(SlingConstants.PROPERTY_USERID, authInfo.getUser());
+ properties.put(AuthenticationInfo.AUTH_TYPE, authInfo.getAuthType());
+
+ EventAdmin localEA = this.eventAdmin;
+ if (localEA != null) {
+ localEA.postEvent(new Event(AuthConstants.TOPIC_LOGIN, properties));
+ }
+ }
+
+ /**
+ * Post an event to let subscribers know that a login failure has occurred. For examples, subscribers
+ * to the {@link AuthConstants#TOPIC_LOGIN_FAILED} event topic may be used to implement a failed login throttling solution.
+ */
+ private void postLoginFailedEvent(final HttpServletRequest request, final AuthenticationInfo authInfo, Exception reason) {
+ // The reason for the failure may be useful to downstream subscribers.
+ AuthenticationHandler.FAILURE_REASON_CODES reason_code = getFailureReasonFromException(authInfo, reason);
+ //if reason_code is null, it is problem some non-login related failure, so don't send the event
+ if (reason_code != null) {
+ final Dictionary properties = new Hashtable();
+ if (authInfo.getUser() != null) {
+ properties.put(SlingConstants.PROPERTY_USERID, authInfo.getUser());
+ }
+ if (authInfo.getAuthType() != null) {
+ properties.put(AuthenticationInfo.AUTH_TYPE, authInfo.getAuthType());
+ }
+ properties.put("reason_code", reason_code.name());
+
+ EventAdmin localEA = this.eventAdmin;
+ if (localEA != null) {
+ localEA.postEvent(new Event(AuthConstants.TOPIC_LOGIN_FAILED, properties));
+ }
+ }
+ }
+
+ /**
+ * Ensures the cookie value is properly quoted for transmission to the
+ * client.
+ *
+ * The problem is, that the character set of cookie values is limited by
+ * RFC 2109 defining that a cookie value must be token or quoted-string
+ * according to RFC-2616:
+ *
+ * token = 1*
+ * separators = "(" | ")" | "<" | ">" | "@"
+ * | "," | ";" | ":" | "\" | <">
+ * | "/" | "[" | "]" | "?" | "="
+ * | "{" | "}" | SP | HT
+ *
+ * quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
+ * qdtext = >
+ * quoted-pair = "\" CHAR
+ *
+ * @param value The cookie value to quote
+ * @return The quoted cookie value
+ * @throws UnsupportedEncodingException
+ * @throws IllegalArgumentException If the cookie value is null
+ * or cannot be quoted, primarily because it contains a quote
+ * sign.
+ */
+ static String quoteCookieValue(final String value) throws UnsupportedEncodingException {
+ // method is package private to enable unit testing
+
+ if (value == null) {
+ throw new IllegalArgumentException("Cookie value may not be null");
+ }
+
+ StringBuilder builder = new StringBuilder(value.length() * 2);
+ builder.append('"');
+ for (int i = 0; i < value.length(); i++) {
+ char c = value.charAt(i);
+ if (c == '"') {
+ builder.append("\\\"");
+ } else if (c == '@') {
+ builder.append(c);
+ } else if (c == 127 || (c < 32 && c != '\t')) {
+ throw new IllegalArgumentException(
+ "Cookie value may not contain CTL character");
+ } else {
+ builder.append(URLEncoder.encode(String.valueOf(c), "UTF-8"));
+ }
+ }
+ builder.append('"');
+
+ return builder.toString();
+ }
+
+ /**
+ * Removes (optional) quotes from a cookie value to get the raw value of the
+ * cookie.
+ *
+ * @param value The cookie value to unquote
+ * @return The unquoted cookie value
+ */
+ static String unquoteCookieValue(String value) {
+ // method is package private to enable unit testing
+
+ // return value unmodified if null or empty
+ if (value == null || value.length() == 0) {
+ return value;
+ }
+
+ if (value.startsWith("\"") && value.endsWith("\"")) {
+ value = value.substring(1, value.length()-1);
+ }
+
+ StringBuilder builder = new StringBuilder();
+ String [] values = value.split("\\\\");
+ for (String v:values) {
+ try {
+ builder.append(URLDecoder.decode(v, "UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ builder.append(v);
+ }
+ }
+ return builder.toString();
+ }
+
+ private static class AuthenticationHandlerTracker extends ServiceTracker {
+
+ private final PathBasedHolderCache authHandlerCache;
+
+ private final HashMap