Index: dna-graph/src/main/java/org/jboss/dna/graph/ExecutionContext.java =================================================================== --- dna-graph/src/main/java/org/jboss/dna/graph/ExecutionContext.java (revision 1377) +++ dna-graph/src/main/java/org/jboss/dna/graph/ExecutionContext.java (working copy) @@ -26,7 +26,6 @@ import java.security.AccessControlContext; import java.security.AccessController; import java.util.UUID; -import javax.security.auth.login.LoginException; import net.jcip.annotations.Immutable; import org.jboss.dna.common.component.ClassLoaderFactory; import org.jboss.dna.common.component.StandardClassLoaderFactory; @@ -300,10 +299,8 @@ * @return the execution context that is identical with this execution context, but with a different security context; never * null * @throws IllegalArgumentException if the name is null - * @throws LoginException if there name is invalid (or there is no login context named "other"), or if the - * default callback handler JAAS property was not set or could not be loaded */ - public ExecutionContext with( SecurityContext securityContext ) throws LoginException { + public ExecutionContext with( SecurityContext securityContext ) { return new ExecutionContext(this, securityContext); } Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrRepository.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrRepository.java (revision 1377) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrRepository.java (working copy) @@ -29,6 +29,7 @@ import java.security.AccessControlException; import java.security.AccessController; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.EnumMap; import java.util.HashMap; @@ -46,7 +47,6 @@ import javax.security.auth.Subject; import javax.security.auth.login.Configuration; import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; import net.jcip.annotations.GuardedBy; import net.jcip.annotations.Immutable; import net.jcip.annotations.ThreadSafe; @@ -57,6 +57,7 @@ import org.jboss.dna.graph.ExecutionContext; import org.jboss.dna.graph.Graph; import org.jboss.dna.graph.JaasSecurityContext; +import org.jboss.dna.graph.SecurityContext; import org.jboss.dna.graph.Subgraph; import org.jboss.dna.graph.connector.RepositoryConnection; import org.jboss.dna.graph.connector.RepositoryConnectionFactory; @@ -137,9 +138,15 @@ /** * The depth of the subgraphs that should be loaded the connectors. The default value is 1. */ - READ_DEPTH; + READ_DEPTH, /** + * A comma-delimited list of default roles provided for anonymous access. A null or empty value for this option means that + * anonymous access is disabled. + */ + ANONYMOUS_USER_ROLES; + + /** * Determine the option given the option name. This does more than {@link Option#valueOf(String)}, since this method first * tries to match the supplied string to the option's {@link Option#name() name}, then the uppercase version of the * supplied string to the option's name, and finally if the supplied string is a camel-case version of the name (e.g., @@ -186,6 +193,11 @@ * The default value for the {@link Option#READ_DEPTH} option is {@value} . */ public static final String READ_DEPTH = "1"; + + /** + * The default value for the {@link Option#READ_DEPTH} option is {@value} . + */ + public static final String ANONYMOUS_USER_ROLES = null; } /** @@ -199,6 +211,7 @@ defaults.put(Option.PROJECT_NODE_TYPES, DefaultOption.PROJECT_NODE_TYPES); defaults.put(Option.JAAS_LOGIN_CONFIG_NAME, DefaultOption.JAAS_LOGIN_CONFIG_NAME); defaults.put(Option.READ_DEPTH, DefaultOption.READ_DEPTH); + defaults.put(Option.ANONYMOUS_USER_ROLES, DefaultOption.ANONYMOUS_USER_ROLES); DEFAULT_OPTIONS = Collections.unmodifiableMap(defaults); } @@ -217,6 +230,7 @@ private final FederatedRepositorySource federatedSource; private final Observer observer; private final NamespaceRegistry persistentRegistry; + private final SecurityContext anonymousUserContext; /** * Creates a JCR repository that uses the supplied {@link RepositoryConnectionFactory repository connection factory} to @@ -411,6 +425,34 @@ this.lockManagers = new ConcurrentHashMap(); this.locksPath = pathFactory.create(pathFactory.createRootPath(), JcrLexicon.SYSTEM, DnaLexicon.LOCKS); + + /* + * Set up the anonymous role, if appropriate + */ + SecurityContext anonymousUserContext = null; + String rawAnonRoles = options != null ? options.get(Option.ANONYMOUS_USER_ROLES) : null; + if (rawAnonRoles != null) { + String[] anonRoles = rawAnonRoles.split("\\s*,\\s*"); + final List roles = Arrays.asList(anonRoles); + if (anonRoles.length > 0) { + anonymousUserContext = new SecurityContext() { + + public String getUserName() { + return null; + } + + public boolean hasRole( String roleName ) { + return roles.contains(roleName); + } + + public void logout() { + } + + }; + } + } + + this.anonymousUserContext = anonymousUserContext; } protected void initializeSystemContent( Graph systemGraph ) { @@ -563,16 +605,16 @@ Map sessionAttributes = new HashMap(); ExecutionContext execContext = null; if (credentials == null) { - try { - Subject subject = Subject.getSubject(AccessController.getContext()); - if (subject == null) { - throw new javax.jcr.LoginException(JcrI18n.mustBeInPrivilegedAction.text()); - } + Subject subject = Subject.getSubject(AccessController.getContext()); + if (subject != null) { execContext = executionContext.with(new JaasSecurityContext(subject)); - } catch (LoginException le) { - // This really can't happen if you're creating the JAAS security context with an existing subject - throw new IllegalStateException(le); } + // Well. There's no JAAS subject. Try using an anonymous user (if that's enabled). + else if (anonymousUserContext != null) { + execContext = executionContext.with(this.anonymousUserContext); + } else { + throw new javax.jcr.LoginException(JcrI18n.mustBeInPrivilegedAction.text()); + } } else { try { if (credentials instanceof SimpleCredentials) { Index: dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties =================================================================== --- dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties (revision 1377) +++ dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties (working copy) @@ -23,7 +23,7 @@ # cannotConvertValue = Cannot convert {0} value to {1} credentialsMustProvideJaasMethod = The Credentials class "{0}" must implement "public LoginContext getLoginContext();", be an instance of "javax.jcr.SimpleCredentials", or be an instance of "org.jboss.dna.jcr.SecurityContextCredentials" -mustBeInPrivilegedAction=login() can only be called successfully from within a java.security.PrivilegedAction +mustBeInPrivilegedAction=login() can only be called successfully from within a java.security.PrivilegedAction or when the ANONYMOUS_USER_ROLES repository option is set credentialsMustReturnLoginContext = The "getLoginContext()" method in Credentials class "{0}" must not return a null defaultWorkspaceName= inputStreamConsumed = This value was already consumed as an input stream Index: dna-jcr/src/test/java/org/jboss/dna/jcr/JcrConfigurationTest.java =================================================================== --- dna-jcr/src/test/java/org/jboss/dna/jcr/JcrConfigurationTest.java (revision 1377) +++ dna-jcr/src/test/java/org/jboss/dna/jcr/JcrConfigurationTest.java (working copy) @@ -249,6 +249,7 @@ options.put(Option.JAAS_LOGIN_CONFIG_NAME, "test"); options.put(Option.PROJECT_NODE_TYPES, DefaultOption.PROJECT_NODE_TYPES); options.put(Option.READ_DEPTH, DefaultOption.READ_DEPTH); + options.put(Option.ANONYMOUS_USER_ROLES, DefaultOption.ANONYMOUS_USER_ROLES); assertThat(repository.getOptions(), is(options)); } Index: dna-jcr/src/test/java/org/jboss/dna/jcr/JcrRepositoryTest.java =================================================================== --- dna-jcr/src/test/java/org/jboss/dna/jcr/JcrRepositoryTest.java (revision 1377) +++ dna-jcr/src/test/java/org/jboss/dna/jcr/JcrRepositoryTest.java (working copy) @@ -26,6 +26,7 @@ import static org.hamcrest.collection.IsArrayContaining.hasItemInArray; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsNull.notNullValue; +import static org.hamcrest.core.IsNull.nullValue; import static org.junit.Assert.assertThat; import java.security.AccessControlContext; import java.security.AccessController; @@ -226,6 +227,19 @@ } @Test + public void shouldAllowLoginWithNoCredentialsIfAnonAccessEnabled() throws Exception { + Map options = new HashMap(); + options.put(JcrRepository.Option.ANONYMOUS_USER_ROLES, JcrSession.DNA_READ_PERMISSION); + JcrRepository repository = new JcrRepository(context, connectionFactory, sourceName, descriptors, options); + + session = (JcrSession)repository.login(); + + assertThat(session, is(notNullValue())); + assertThat(session.getUserID(), is(nullValue())); + + } + + @Test public void shouldAllowLoginWithProperCredentials() throws Exception { repository.login(credentials); repository.login(new SecurityContextCredentials( Index: web/dna-web-jcr-rest/src/main/java/org/jboss/dna/web/jcr/rest/JcrResources.java =================================================================== --- web/dna-web-jcr-rest/src/main/java/org/jboss/dna/web/jcr/rest/JcrResources.java (revision 1377) +++ web/dna-web-jcr-rest/src/main/java/org/jboss/dna/web/jcr/rest/JcrResources.java (working copy) @@ -185,13 +185,7 @@ String rawRepositoryName, String rawWorkspaceName ) throws RepositoryException { assert request != null; - assert request.getUserPrincipal() != null : "Request must be authorized"; - // Sanity check - if (request.getUserPrincipal() == null) { - throw new UnauthorizedException("Client is not authorized"); - } - return RepositoryFactory.getSession(request, repositoryNameFor(rawRepositoryName), workspaceNameFor(rawWorkspaceName)); } Index: web/dna-web-jcr-rest/src/main/java/org/jboss/dna/web/jcr/rest/spi/DnaJcrRepositoryProvider.java =================================================================== --- web/dna-web-jcr-rest/src/main/java/org/jboss/dna/web/jcr/rest/spi/DnaJcrRepositoryProvider.java (revision 1377) +++ web/dna-web-jcr-rest/src/main/java/org/jboss/dna/web/jcr/rest/spi/DnaJcrRepositoryProvider.java (working copy) @@ -38,7 +38,6 @@ import org.jboss.dna.jcr.SecurityContextCredentials; import org.jboss.dna.web.jcr.rest.ServletSecurityContext; import org.jboss.resteasy.spi.NotFoundException; -import org.jboss.resteasy.spi.UnauthorizedException; import org.xml.sax.SAXException; /** @@ -102,13 +101,7 @@ String repositoryName, String workspaceName ) throws RepositoryException { assert request != null; - assert request.getUserPrincipal() != null : "Request must be authorized"; - // Sanity check in case assertions are disabled - if (request.getUserPrincipal() == null) { - throw new UnauthorizedException("Client is not authorized"); - } - Repository repository; try { @@ -118,6 +111,11 @@ throw new NotFoundException(re.getMessage(), re); } + // If there's no authenticated user, try an anonymous login + if (request.getUserPrincipal() == null) { + return repository.login(workspaceName); + } + return repository.login(new SecurityContextCredentials(new ServletSecurityContext(request)), workspaceName); }