Skip to content

Commit

Permalink
JAMES-1695 Implements JMAP account auto-creation when using JWT
Browse files Browse the repository at this point in the history
  • Loading branch information
mbaechler authored and aduprat committed Mar 1, 2016
1 parent 39b1950 commit 920e261
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 19 deletions.
Expand Up @@ -344,6 +344,22 @@ public void getMustReturnEndpointsWhenValidJwtAuthorizationHeader() throws Excep
.statusCode(200); .statusCode(200);
} }


@Test
public void getMustReturnEndpointsWhenValidUnkwnonUserJwtAuthorizationHeader() throws Exception {
String token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMzM3IiwibmFtZSI6Ik5ldyBVc2VyIn0.ci8U04EOWKpi_y"
+ "faKs8fnCBcu1mWs8fvf-t9SDP2kkvDDfD-ya4sEGn4ueCp2dA2ndefZfVu_IfvdlVtxqzSf0tQ-dFKrIe-OtSKhI2otjWctLtk9A"
+ "G7jpWkXoDgr5IOVmsqg37Zxc2bgkLkC5FJqV6oCp51TNQTH6zZbXIUeuGFbHj2-iJeX8sACKTQB0llwc6TFm7GYUF03rv4DfJjqp"
+ "Kd0g8RdnlevSOjV-gGzvKEItugtexS5pgOZ2GYcvqEUDb9EnQR7Qe2EzPAX_FCJfGhlv7bDQlTgOHHAjqw2lD4-zeAznw-3wlYLS"
+ "zhi4ivvPjT-y2T5wnnhzeeYOpYOQ";

given()
.header("Authorization", "Bearer " + token)
.when()
.get("/authentication")
.then()
.statusCode(200);
}

@Test @Test
public void getMustReturnBadCredentialsWhenInvalidJwtAuthorizationHeader() throws Exception { public void getMustReturnBadCredentialsWhenInvalidJwtAuthorizationHeader() throws Exception {
String token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.T04BTk" + String token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.T04BTk" +
Expand Down
Expand Up @@ -22,8 +22,9 @@
import static com.jayway.restassured.RestAssured.given; import static com.jayway.restassured.RestAssured.given;
import static com.jayway.restassured.config.EncoderConfig.encoderConfig; import static com.jayway.restassured.config.EncoderConfig.encoderConfig;
import static com.jayway.restassured.config.RestAssuredConfig.newConfig; import static com.jayway.restassured.config.RestAssuredConfig.newConfig;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.isEmptyOrNullString; import static org.hamcrest.Matchers.isEmptyOrNullString;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
Expand Down Expand Up @@ -243,31 +244,28 @@ public void getMailboxesShouldReturnEmptyListWhenNoMailboxes() throws Exception
} }


@Test @Test
public void getMailboxesShouldReturnEmptyListWhenNoMailboxesWithJWTAuthWorkflow() { public void getMailboxesShouldReturnDefaultMailboxesWhenAuthenticatedUserDoesntHaveAnAccountYet() throws Exception {


String authToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.T04BTk" + String token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMzM3QGRvbWFpbi50bGQiLCJuYW1lIjoiTmV3IFVzZXIif"
"LXkJj24coSZkK13RfG25lpvmSl2MJ7N10KpBk9_-95EGYZdog-BDAn3PJzqVw52z-Bwjh4VOj1-j7cURu0cT4jXehhUrlCxS4n7QHZ" + + "Q.fxNWNzksXyCij2ooVi-QfGe9vTicF2N9FDtWSJdjWTjhwoQ_i0dgiT8clp4dtOJzy78hB2UkAW-iq7z3PR_Gz0qFah7EbYoEs"
"DN_bsEYGu7KzjWTpTsUiHe-rN7izXVFxDGG1TGwlmBCBnPW-EFCf9ylUsJi0r2BKNdaaPRfMIrHptH1zJBkkUziWpBN1RNLjmvlAUf" + + "5lQs1UlhNGCRTvIsyR8qHUXtA6emw9x0nuMnswtyXhzoA-cEHCArrMxMeWhTYi2l4od3G8Irrvu1Yc5hKLwLgPdnImbKyB5a89T"
"49t1Tbv21ZqYM5Ht2vrhJWczFbuC-TD-8zJkXhjTmA1GVgomIX5dx1cH-dZX1wANNmshUJGHgepWlPU-5VIYxPEhb219RMLJIELMY2" + + "vzuZE8-FVyMmhlaJA2T1GpbsaUnfE1ki_bBzqMHTD_Ob7oSVzz2UOiOeL-ombn1X9GbYQ2I-Ob4V84WHONYxw0VjPHlj9saZ2n7"
"qNOR8Q31ydinyqzXvCSzVJOf6T60-w"; + "2RJTBsIo6flJT-MchaEvTYBvuV_wlCCQYjI1g7mdeD6aXfw";

given() given()
.accept(ContentType.JSON) .accept(ContentType.JSON)
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.header("Authorization", "Bearer " + authToken) .header("Authorization", "Bearer " + token)
.body("[[\"getMailboxes\", {}, \"#0\"]]") .body("[[\"getMailboxes\", {}, \"#0\"]]")
.when() .when()
.post("/jmap") .post("/jmap")
.then() .then()
.statusCode(200) .statusCode(200)
.body(NAME, equalTo("mailboxes")) .body(NAME, equalTo("mailboxes"))
.body(ARGUMENTS + ".accountId", isEmptyOrNullString()) .body(ARGUMENTS + ".list", hasSize(3))
.body(ARGUMENTS + ".state", isEmptyOrNullString()) .body(ARGUMENTS + ".list.name", hasItems("INBOX", "Outbox", "Sent"));
.body(ARGUMENTS + ".notFound", isEmptyOrNullString())
.body(ARGUMENTS + ".list", hasSize(0));
} }



@Test @Test
public void getMailboxesShouldErrorWithBadJWTToken() { public void getMailboxesShouldErrorWithBadJWTToken() {


Expand Down
@@ -0,0 +1,121 @@
/****************************************************************
* 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.james.jmap;

import java.io.IOException;
import java.util.UUID;
import java.util.function.Function;

import javax.inject.Inject;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.apache.james.mailbox.MailboxManager;
import org.apache.james.mailbox.MailboxSession;
import org.apache.james.mailbox.MailboxSession.User;
import org.apache.james.mailbox.exception.BadCredentialsException;
import org.apache.james.mailbox.exception.MailboxException;
import org.apache.james.mailbox.model.MailboxPath;
import org.apache.james.user.api.UsersRepository;
import org.apache.james.user.api.UsersRepositoryException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;

public class FirstUserConnectionFilter implements Filter {

private static final ImmutableList<String> DEFAULT_MAILBOXES = ImmutableList.of("INBOX", "Outbox", "Sent");
private static final Logger LOGGER = LoggerFactory.getLogger(FirstUserConnectionFilter.class);
private final UsersRepository usersRepository;
private final MailboxManager mailboxManager;

@Inject
private FirstUserConnectionFilter(UsersRepository usersRepository, MailboxManager mailboxManager) {
this.usersRepository = usersRepository;
this.mailboxManager = mailboxManager;
}

@Override
public void init(FilterConfig filterConfig) throws ServletException {
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
MailboxSession session = (MailboxSession) request.getAttribute(AuthenticationFilter.MAILBOX_SESSION);
createAccountIfNeeded(session);
chain.doFilter(request, response);
}

private void createAccountIfNeeded(MailboxSession session) {
try {
User user = session.getUser();
if (needsAccountCreation(user)) {
createAccount(user);
}
} catch (UsersRepositoryException|MailboxException e) {
throw Throwables.propagate(e);
}
}

private boolean needsAccountCreation(User user) throws UsersRepositoryException {
return !usersRepository.contains(user.getUserName());
}

private void createAccount(User user) throws UsersRepositoryException, BadCredentialsException, MailboxException {
createUser(user);
createDefaultMailboxes(user);
}

private void createUser(User user) throws UsersRepositoryException {
usersRepository.addUser(user.getUserName(), generatePassword());
}

private String generatePassword() {
return UUID.randomUUID().toString();
}

private void createDefaultMailboxes(User user) throws BadCredentialsException, MailboxException {
MailboxSession session = mailboxManager.createSystemSession(user.getUserName(), LOGGER);
DEFAULT_MAILBOXES.stream()
.map(toMailboxPath(session))
.forEach(mailboxPath -> createMailbox(mailboxPath, session));
}

private Function<String, MailboxPath> toMailboxPath(MailboxSession session) {
return mailbox -> new MailboxPath(session.getPersonalSpace(), session.getUser().getUserName(), mailbox);
}

private void createMailbox(MailboxPath mailboxPath, MailboxSession session) {
try {
mailboxManager.createMailbox(mailboxPath, session);
} catch (MailboxException e) {
throw Throwables.propagate(e);
}
}

@Override
public void destroy() {
}
}
Expand Up @@ -30,7 +30,6 @@
import org.apache.james.http.jetty.Configuration; import org.apache.james.http.jetty.Configuration;
import org.apache.james.http.jetty.Configuration.Builder; import org.apache.james.http.jetty.Configuration.Builder;
import org.apache.james.http.jetty.JettyHttpServer; import org.apache.james.http.jetty.JettyHttpServer;
import org.apache.james.http.jetty.LambdaFilter;
import org.apache.james.lifecycle.api.Configurable; import org.apache.james.lifecycle.api.Configurable;


import com.google.common.base.Throwables; import com.google.common.base.Throwables;
Expand All @@ -44,9 +43,8 @@ public class JMAPServer implements Configurable {
@Inject @Inject
private JMAPServer(PortConfiguration portConfiguration, private JMAPServer(PortConfiguration portConfiguration,
AuthenticationServlet authenticationServlet, JMAPServlet jmapServlet, AuthenticationServlet authenticationServlet, JMAPServlet jmapServlet,
AuthenticationFilter authenticationFilter) { AuthenticationFilter authenticationFilter, FirstUserConnectionFilter firstUserConnectionFilter) {


LambdaFilter provisionUserFilter = (req, resp, chain) -> chain.doFilter(req, resp);
server = JettyHttpServer.create( server = JettyHttpServer.create(
configurationBuilderFor(portConfiguration) configurationBuilderFor(portConfiguration)
.serve("/authentication") .serve("/authentication")
Expand All @@ -58,7 +56,7 @@ private JMAPServer(PortConfiguration portConfiguration,
.with(jmapServlet) .with(jmapServlet)
.filter("/jmap") .filter("/jmap")
.with(new AllowAllCrossOriginRequests(bypass(authenticationFilter).on("OPTIONS").only())) .with(new AllowAllCrossOriginRequests(bypass(authenticationFilter).on("OPTIONS").only()))
.and(provisionUserFilter) .and(firstUserConnectionFilter)
.only() .only()
.build()); .build());
} }
Expand Down

0 comments on commit 920e261

Please sign in to comment.