org.entcore.auth.controllers.AuthController.java Source code

Java tutorial

Introduction

Here is the source code for org.entcore.auth.controllers.AuthController.java

Source

/* Copyright  "Open Digital Education", 2014
 *
 * This program is published by "Open Digital Education".
 * You must indicate the name of the software and the company in any production /contribution
 * using the software and indicate on the home page of the software industry in question,
 * "powered by Open Digital Education" with a reference to the website: https://opendigitaleducation.com/.
 *
 * This program is free software, licensed under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation, version 3 of the License.
 *
 * You can redistribute this application and/or modify it since you respect the terms of the GNU Affero General Public License.
 * If you modify the source code and then use this modified source code in your creation, you must make available the source code of your modifications.
 *
 * You should have received a copy of the GNU Affero General Public License along with the software.
 * If not, please see : <http://www.gnu.org/licenses/>. Full compliance requires reading the terms of this license and following its directives.
    
 *
 */

package org.entcore.auth.controllers;

import static fr.wseduc.webutils.Utils.isNotEmpty;
import static fr.wseduc.webutils.Utils.isEmpty;
import static org.entcore.auth.oauth.OAuthAuthorizationResponse.code;
import static org.entcore.auth.oauth.OAuthAuthorizationResponse.invalidRequest;
import static org.entcore.auth.oauth.OAuthAuthorizationResponse.invalidScope;
import static org.entcore.auth.oauth.OAuthAuthorizationResponse.serverError;
import static org.entcore.auth.oauth.OAuthAuthorizationResponse.unauthorizedClient;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import fr.wseduc.bus.BusAddress;
import fr.wseduc.mongodb.MongoDb;
import fr.wseduc.rs.Delete;
import fr.wseduc.rs.Get;
import fr.wseduc.rs.Post;
import fr.wseduc.rs.Put;
import fr.wseduc.webutils.*;
import fr.wseduc.webutils.http.BaseController;
import fr.wseduc.webutils.http.Renders;
import fr.wseduc.webutils.http.response.DefaultResponseHandler;
import fr.wseduc.webutils.logging.Tracer;
import fr.wseduc.webutils.logging.TracerFactory;
import fr.wseduc.webutils.request.RequestUtils;

import io.vertx.core.shareddata.LocalMap;
import jp.eisbahn.oauth2.server.async.Handler;
import org.entcore.auth.adapter.ResponseAdapterFactory;
import org.entcore.auth.adapter.UserInfoAdapter;
import org.entcore.auth.services.OpenIdConnectService;
import org.entcore.auth.services.impl.DefaultOpendIdConnectService;
import org.entcore.common.events.EventStore;
import org.entcore.common.http.filter.IgnoreCsrf;
import org.entcore.common.neo4j.Neo4j;
import org.entcore.common.utils.MapFactory;
import org.entcore.common.validation.StringValidation;

import fr.wseduc.security.ActionType;
import jp.eisbahn.oauth2.server.data.DataHandler;
import jp.eisbahn.oauth2.server.data.DataHandlerFactory;
import jp.eisbahn.oauth2.server.endpoint.ProtectedResource;
import jp.eisbahn.oauth2.server.endpoint.Token;
import jp.eisbahn.oauth2.server.endpoint.Token.Response;
import jp.eisbahn.oauth2.server.exceptions.OAuthError;
import jp.eisbahn.oauth2.server.exceptions.Try;
import jp.eisbahn.oauth2.server.fetcher.accesstoken.AccessTokenFetcherProvider;
import jp.eisbahn.oauth2.server.fetcher.accesstoken.impl.DefaultAccessTokenFetcherProvider;
import jp.eisbahn.oauth2.server.fetcher.clientcredential.ClientCredentialFetcher;
import jp.eisbahn.oauth2.server.fetcher.clientcredential.ClientCredentialFetcherImpl;
import jp.eisbahn.oauth2.server.granttype.GrantHandlerProvider;
import jp.eisbahn.oauth2.server.granttype.impl.DefaultGrantHandlerProvider;
import jp.eisbahn.oauth2.server.models.AuthInfo;
import jp.eisbahn.oauth2.server.models.Request;

import io.vertx.core.Vertx;
import io.vertx.core.eventbus.EventBus;
import io.vertx.core.eventbus.Message;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.impl.VertxInternal;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.core.spi.cluster.ClusterManager;
import org.entcore.auth.oauth.HttpServerRequestAdapter;
import org.entcore.auth.oauth.JsonRequestAdapter;
import org.entcore.auth.oauth.OAuthDataHandler;
import org.entcore.auth.oauth.OAuthDataHandlerFactory;
import org.entcore.auth.pojo.SendPasswordDestination;
import org.entcore.auth.users.UserAuthAccount;

import fr.wseduc.webutils.request.CookieHelper;
import fr.wseduc.webutils.security.SecureHttpServerRequest;

import org.entcore.common.user.UserUtils;
import org.entcore.common.user.UserInfos;

import fr.wseduc.security.SecuredAction;
import org.vertx.java.core.http.RouteMatcher;

public class AuthController extends BaseController {

    private DataHandlerFactory oauthDataFactory;
    private Token token;
    private ProtectedResource protectedResource;
    private UserAuthAccount userAuthAccount;
    private static final Tracer trace = TracerFactory.getTracer("auth");
    private EventStore eventStore;
    private Map<Object, Object> invalidEmails;
    private JsonArray authorizedHostsLogin;

    public enum AuthEvent {
        ACTIVATION, LOGIN, SMS
    }

    private Pattern passwordPattern;
    private String smsProvider;
    private boolean slo;
    private List<String> internalAddress;
    private boolean checkFederatedLogin = false;

    @Override
    public void init(Vertx vertx, JsonObject config, RouteMatcher rm,
            Map<String, fr.wseduc.webutils.security.SecuredAction> securedActions) {
        super.init(vertx, config, rm, securedActions);
        JsonObject oic = config.getJsonObject("openid-connect");
        OpenIdConnectService openIdConnectService = (oic != null)
                ? new DefaultOpendIdConnectService(oic.getString("iss"), vertx, oic.getString("keys"))
                : null;
        checkFederatedLogin = config.getBoolean("check-federated-login", false);
        oauthDataFactory = new OAuthDataHandlerFactory(Neo4j.getInstance(), MongoDb.getInstance(),
                openIdConnectService, checkFederatedLogin);
        GrantHandlerProvider grantHandlerProvider = new DefaultGrantHandlerProvider();
        ClientCredentialFetcher clientCredentialFetcher = new ClientCredentialFetcherImpl();
        token = new Token();
        token.setDataHandlerFactory(oauthDataFactory);
        token.setGrantHandlerProvider(grantHandlerProvider);
        token.setClientCredentialFetcher(clientCredentialFetcher);
        AccessTokenFetcherProvider accessTokenFetcherProvider = new DefaultAccessTokenFetcherProvider();
        protectedResource = new ProtectedResource();
        protectedResource.setDataHandlerFactory(oauthDataFactory);
        protectedResource.setAccessTokenFetcherProvider(accessTokenFetcherProvider);
        passwordPattern = Pattern.compile(config.getString("passwordRegex", ".{8}.*"));
        LocalMap<Object, Object> server = vertx.sharedData().getLocalMap("server");
        if (server != null && server.get("smsProvider") != null)
            smsProvider = (String) server.get("smsProvider");
        slo = config.getBoolean("slo", false);
        //      if (server != null) {
        //         Boolean cluster = (Boolean) server.get("cluster");
        //         if (Boolean.TRUE.equals(cluster)) {
        //            ClusterManager cm = ((VertxInternal) vertx).clusterManager();
        //            invalidEmails = cm.getSyncMap("invalidEmails");
        //         } else {
        //            invalidEmails = vertx.sharedData().getMap("invalidEmails");
        //         }
        //      } else {
        invalidEmails = MapFactory.getSyncClusterMap("invalidEmails", vertx);
        internalAddress = config.getJsonArray("internalAddress",
                new fr.wseduc.webutils.collections.JsonArray().add("localhost").add("127.0.0.1")).getList();
    }

    @Get("/oauth2/auth")
    public void authorize(final HttpServerRequest request) {
        final String responseType = request.params().get("response_type");
        final String clientId = request.params().get("client_id");
        final String redirectUri = request.params().get("redirect_uri");
        final String scope = request.params().get("scope");
        final String state = request.params().get("state");
        if ("code".equals(responseType) && clientId != null && !clientId.trim().isEmpty()) {
            if (isNotEmpty(scope)) {
                final DataHandler data = oauthDataFactory.create(new HttpServerRequestAdapter(request));
                data.validateClientById(clientId, new Handler<Boolean>() {

                    @Override
                    public void handle(Boolean clientValid) {
                        if (Boolean.TRUE.equals(clientValid)) {
                            UserUtils.getUserInfos(eb, request, new io.vertx.core.Handler<UserInfos>() {

                                @Override
                                public void handle(UserInfos user) {
                                    if (user != null && user.getUserId() != null) {
                                        ((OAuthDataHandler) data).createOrUpdateAuthInfo(clientId, user.getUserId(),
                                                scope, redirectUri, new Handler<AuthInfo>() {

                                                    @Override
                                                    public void handle(AuthInfo auth) {
                                                        if (auth != null) {
                                                            code(request, redirectUri, auth.getCode(), state);
                                                        } else {
                                                            serverError(request, redirectUri, state);
                                                        }
                                                    }
                                                });
                                    } else {
                                        viewLogin(request, null, request.uri());
                                    }
                                }
                            });
                        } else {
                            unauthorizedClient(request, redirectUri, state);
                        }
                    }
                });
            } else {
                invalidScope(request, redirectUri, state);
            }
        } else {
            invalidRequest(request, redirectUri, state);
        }
    }

    @Post("/oauth2/token")
    public void token(final HttpServerRequest request) {
        request.setExpectMultipart(true);
        request.endHandler(new io.vertx.core.Handler<Void>() {

            @Override
            public void handle(Void v) {
                Request req = new HttpServerRequestAdapter(request);
                token.handleRequest(req, new Handler<Response>() {

                    @Override
                    public void handle(Response response) {
                        renderJson(request, new JsonObject(response.getBody()), response.getCode());
                    }
                });
            }
        });
    }

    private void loginResult(final HttpServerRequest request, String error, String callBack) {
        final JsonObject context = new JsonObject();
        if (callBack != null && !callBack.trim().isEmpty()) {
            try {
                context.put("callBack", URLEncoder.encode(callBack, "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                log.error(e.getMessage(), e);
            }
        }
        if (error != null && !error.trim().isEmpty()) {
            context.put("error", new JsonObject().put("message",
                    I18n.getInstance().translate(error, getHost(request), I18n.acceptLanguage(request))));
        }
        UserUtils.getUserInfos(eb, request, new io.vertx.core.Handler<UserInfos>() {
            @Override
            public void handle(UserInfos user) {
                context.put("notLoggedIn", user == null);
                renderView(request, context, "login.html", null);
            }
        });
    }

    @Get("/context")
    public void context(final HttpServerRequest request) {
        final JsonObject context = new JsonObject();
        context.put("callBack", config.getJsonObject("authenticationServer").getString("loginCallback"));
        context.put("cgu", config.getBoolean("cgu", true));
        context.put("passwordRegex", passwordPattern.toString());
        context.put("mandatory", config.getJsonObject("mandatory", new JsonObject()));
        renderJson(request, context);
    }

    @Get("/admin-welcome-message")
    public void adminWelcomeMessage(final HttpServerRequest request) {
        renderView(request);
    }

    private void viewLogin(final HttpServerRequest request, String error, String callBack) {
        final JsonObject context = new JsonObject();
        if (callBack != null && !callBack.trim().isEmpty()) {
            try {
                context.put("callBack", URLEncoder.encode(callBack, "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                log.error(e.getMessage(), e);
            }
        }
        if (error != null && !error.trim().isEmpty()) {
            context.put("error", new JsonObject().put("message",
                    I18n.getInstance().translate(error, getHost(request), I18n.acceptLanguage(request))));
        }
        UserUtils.getUserInfos(eb, request, new io.vertx.core.Handler<UserInfos>() {
            @Override
            public void handle(UserInfos user) {
                context.put("notLoggedIn", user == null);
                renderView(request, context, "login.html", null);
            }
        });
    }

    @Get("/login")
    public void login(final HttpServerRequest request) {
        final String host = getHost(request);
        if (authorizedHostsLogin != null && isNotEmpty(host) && !authorizedHostsLogin.contains(host)) {
            redirect(request, pathPrefix + "/openid/login");
        } else {
            UserUtils.getUserInfos(eb, request, new io.vertx.core.Handler<UserInfos>() {
                @Override
                public void handle(UserInfos user) {
                    if (user == null || !config.getBoolean("auto-redirect", true)) {
                        viewLogin(request, null, request.params().get("callBack"));
                    } else {
                        String callBack = request.params().get("callBack");
                        if (isEmpty(callBack)) {
                            callBack = getScheme(request) + "://" + host;
                        }
                        redirect(request, callBack, "");
                    }
                }
            });
        }
    }

    @Post("/login")
    public void loginSubmit(final HttpServerRequest request) {
        request.setExpectMultipart(true);
        request.endHandler(new io.vertx.core.Handler<Void>() {
            @Override
            public void handle(Void v) {
                String c = request.formAttributes().get("callBack");
                final StringBuilder callBack = new StringBuilder();
                if (c != null && !c.trim().isEmpty()) {
                    try {
                        callBack.append(URLDecoder.decode(c, "UTF-8"));
                    } catch (UnsupportedEncodingException ex) {
                        log.error(ex.getMessage(), ex);
                        callBack.append(config.getJsonObject("authenticationServer").getString("loginCallback"));
                    }
                } else {
                    callBack.append(config.getJsonObject("authenticationServer").getString("loginCallback"));
                }
                DataHandler data = oauthDataFactory.create(new HttpServerRequestAdapter(request));
                final String login = request.formAttributes().get("email");
                final String password = request.formAttributes().get("password");
                data.getUserId(login, password, new Handler<String>() {

                    @Override
                    public void handle(final String userId) {
                        final String c = callBack.toString();
                        if (userId != null && !userId.trim().isEmpty()) {
                            handleGetUserId(login, userId, request, c);
                        } else {
                            // try activation with login
                            userAuthAccount.matchActivationCode(login, password,
                                    new io.vertx.core.Handler<Boolean>() {
                                        @Override
                                        public void handle(Boolean passIsActivationCode) {
                                            if (passIsActivationCode) {
                                                handleMatchActivationCode(login, password, request);
                                            } else {
                                                // try activation with loginAlias
                                                userAuthAccount.matchActivationCodeByLoginAlias(login, password,
                                                        new io.vertx.core.Handler<Boolean>() {
                                                            @Override
                                                            public void handle(Boolean passIsActivationCode) {
                                                                if (passIsActivationCode) {
                                                                    handleMatchActivationCode(login, password,
                                                                            request);
                                                                } else {
                                                                    // try reset with login
                                                                    userAuthAccount.matchResetCode(login, password,
                                                                            new io.vertx.core.Handler<Boolean>() {
                                                                                @Override
                                                                                public void handle(
                                                                                        Boolean passIsResetCode) {
                                                                                    if (passIsResetCode) {
                                                                                        handleMatchResetCode(login,
                                                                                                password, request);
                                                                                    } else {
                                                                                        // try reset with loginAlias
                                                                                        userAuthAccount
                                                                                                .matchResetCodeByLoginAlias(
                                                                                                        login,
                                                                                                        password,
                                                                                                        new io.vertx.core.Handler<Boolean>() {
                                                                                                            @Override
                                                                                                            public void handle(
                                                                                                                    Boolean passIsResetCode) {
                                                                                                                if (passIsResetCode) {
                                                                                                                    handleMatchResetCode(
                                                                                                                            login,
                                                                                                                            password,
                                                                                                                            request);
                                                                                                                } else {
                                                                                                                    trace.info(
                                                                                                                            "Erreur de connexion pour l'utilisateur "
                                                                                                                                    + login);
                                                                                                                    loginResult(
                                                                                                                            request,
                                                                                                                            "auth.error.authenticationFailed",
                                                                                                                            c);
                                                                                                                }
                                                                                                            }
                                                                                                        });
                                                                                    }
                                                                                }
                                                                            });
                                                                }
                                                            }
                                                        });
                                            }
                                        }
                                    });

                        }
                    }
                });

            }
        });
    }

    private void handleGetUserId(String login, String userId, HttpServerRequest request, String callback) {
        trace.info("Connexion de l'utilisateur " + login);
        userAuthAccount.storeDomain(userId, Renders.getHost(request), Renders.getScheme(request),
                new io.vertx.core.Handler<Boolean>() {
                    public void handle(Boolean ok) {
                        if (!ok) {
                            trace.error(
                                    "[Auth](loginSubmit) Error while storing last known domain for user " + userId);
                        }
                    }
                });
        eventStore.createAndStoreEvent(AuthEvent.LOGIN.name(), login);
        createSession(userId, request, callback);
    }

    private void handleMatchActivationCode(String login, String password, HttpServerRequest request) {
        trace.info("Code d'activation entr pour l'utilisateur " + login);
        final JsonObject json = new JsonObject();
        json.put("activationCode", password);
        json.put("login", login);
        if (config.getBoolean("cgu", true)) {
            json.put("cgu", true);
        }
        UserUtils.getUserInfos(eb, request, new io.vertx.core.Handler<UserInfos>() {
            @Override
            public void handle(UserInfos user) {
                json.put("notLoggedIn", user == null);
                renderView(request, json, "activation.html", null);
            }
        });
    }

    private void handleMatchResetCode(String login, String password, HttpServerRequest request) {
        redirect(request, "/auth/reset/" + password + "?login=" + login);
    }

    private void createSession(String userId, final HttpServerRequest request, final String callBack) {
        UserUtils.createSession(eb, userId, new io.vertx.core.Handler<String>() {

            @Override
            public void handle(String sessionId) {
                if (sessionId != null && !sessionId.trim().isEmpty()) {
                    boolean rememberMe = "true".equals(request.formAttributes().get("rememberMe"));
                    long timeout = rememberMe ? 3600l * 24 * 365 : config.getLong("cookie_timeout", Long.MIN_VALUE);
                    CookieHelper.getInstance().setSigned("oneSessionId", sessionId, timeout, request);
                    CookieHelper.set("authenticated", "true", timeout, request);
                    redirect(request,
                            callBack.matches("https?://[0-9a-zA-Z\\.\\-_]+/auth/login/?(\\?.*)?")
                                    ? callBack.replaceFirst("/auth/login", "")
                                    : callBack,
                            "");
                } else {
                    loginResult(request, "auth.error.authenticationFailed", callBack);
                }
            }
        });
    }

    @Get("/logout")
    public void logout(final HttpServerRequest request) {
        final String c = request.params().get("callback");
        if (slo) {
            UserUtils.getUserInfos(eb, request, new io.vertx.core.Handler<UserInfos>() {
                @Override
                public void handle(UserInfos event) {
                    if (event != null && Boolean.TRUE.equals(event.getFederated())
                            && !request.params().contains("SAMLRequest")) {
                        if (config.containsKey("openid-federate")) {
                            redirect(request, "/auth/openid/slo?callback=" + c);
                        } else {
                            redirect(request, "/auth/saml/slo?callback=" + c);
                        }
                    } else {
                        String c1 = c;
                        if (c1 != null && c1.endsWith("service=")) { // OMT hack
                            try {
                                c1 += URLEncoder.encode(getScheme(request) + "://" + getHost(request), "UTF-8");
                            } catch (UnsupportedEncodingException e) {
                                log.error(e.getMessage(), e);
                            }
                        }
                        logoutCallback(request, c1, config, eb);
                    }
                }
            });
        } else {
            logoutCallback(request, c, config, eb);
        }
    }

    public static void logoutCallback(final HttpServerRequest request, String c, JsonObject config, EventBus eb) {
        final String sessionId = CookieHelper.getInstance().getSigned("oneSessionId", request);
        final StringBuilder callback = new StringBuilder();
        if (c != null && !c.trim().isEmpty()) {
            if (c.contains("_current-domain_")) {
                c = c.replaceAll("_current\\-domain_", request.headers().get("Host"));
            }
            try {
                callback.append(URLDecoder.decode(c, "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                log.error(e.getMessage(), e);
                callback.append(config.getJsonObject("authenticationServer").getString("logoutCallback", "/"));
            }
        } else {
            callback.append(config.getJsonObject("authenticationServer").getString("logoutCallback", "/"));
        }

        if (sessionId != null && !sessionId.trim().isEmpty()) {
            UserUtils.deleteSession(eb, sessionId, new io.vertx.core.Handler<Boolean>() {

                @Override
                public void handle(Boolean deleted) {
                    if (Boolean.TRUE.equals(deleted)) {
                        CookieHelper.set("oneSessionId", "", 0l, request);
                        CookieHelper.set("authenticated", "", 0l, request);
                    }
                    redirect(request, callback.toString(), "");
                }
            });
        } else {
            redirect(request, callback.toString(), "");
        }
    }

    @Get("/oauth2/userinfo")
    @SecuredAction(value = "auth.user.info", type = ActionType.AUTHENTICATED)
    public void userInfo(final HttpServerRequest request) {
        UserUtils.getSession(eb, request, new io.vertx.core.Handler<JsonObject>() {

            @Override
            public void handle(JsonObject infos) {
                if (infos != null) {
                    JsonObject info;
                    UserInfoAdapter adapter = ResponseAdapterFactory.getUserInfoAdapter(request);
                    if (request instanceof SecureHttpServerRequest) {
                        SecureHttpServerRequest sr = (SecureHttpServerRequest) request;
                        String clientId = sr.getAttribute("client_id");
                        info = adapter.getInfo(infos, clientId);
                    } else {
                        info = adapter.getInfo(infos, null);
                    }
                    renderJson(request, info);
                } else {
                    unauthorized(request);
                }
            }
        });
    }

    @Get("/internal/userinfo")
    @SecuredAction(value = "auth.user.info", type = ActionType.AUTHENTICATED)
    public void internalUserInfo(final HttpServerRequest request) {
        if (!(request instanceof SecureHttpServerRequest) || !internalAddress.contains(getIp(request))) {
            forbidden(request);
            return;
        }
        UserUtils.getSessionByUserId(eb, ((SecureHttpServerRequest) request).getAttribute("remote_user"),
                new io.vertx.core.Handler<JsonObject>() {

                    @Override
                    public void handle(JsonObject infos) {
                        if (infos != null) {
                            JsonObject info;
                            UserInfoAdapter adapter = ResponseAdapterFactory.getUserInfoAdapter(request);
                            SecureHttpServerRequest sr = (SecureHttpServerRequest) request;
                            String clientId = sr.getAttribute("client_id");
                            info = adapter.getInfo(infos, clientId);
                            renderJson(request, info);
                        } else {
                            unauthorized(request);
                        }
                    }
                });
    }

    @BusAddress("wse.oauth")
    public void oauthResourceServer(final Message<JsonObject> message) {
        if (message.body() == null) {
            message.reply(new JsonObject());
            return;
        }
        validToken(message);
    }

    private void validToken(final Message<JsonObject> message) {
        protectedResource.handleRequest(new JsonRequestAdapter(message.body()),
                new jp.eisbahn.oauth2.server.async.Handler<Try<OAuthError, ProtectedResource.Response>>() {

                    @Override
                    public void handle(Try<OAuthError, ProtectedResource.Response> resp) {
                        ProtectedResource.Response response;
                        try {
                            response = resp.get();
                            JsonObject r = new JsonObject().put("status", "ok")
                                    .put("client_id", response.getClientId())
                                    .put("remote_user", response.getRemoteUser()).put("scope", response.getScope());
                            message.reply(r);
                        } catch (OAuthError e) {
                            message.reply(new JsonObject().put("error", e.getType()));
                        }
                    }
                });
    }

    @Get("/activation")
    public void activeAccount(final HttpServerRequest request) {
        final JsonObject json = new JsonObject();
        if (request.params().contains("activationCode")) {
            json.put("activationCode", request.params().get("activationCode"));
        }
        if (request.params().contains("login")) {
            json.put("login", request.params().get("login"));
        }
        if (config.getBoolean("cgu", true)) {
            json.put("cgu", true);
        }
        UserUtils.getUserInfos(eb, request, new io.vertx.core.Handler<UserInfos>() {
            @Override
            public void handle(UserInfos user) {
                json.put("notLoggedIn", user == null);
                renderView(request, json);
            }
        });
    }

    @Post("/activation")
    public void activeAccountSubmit(final HttpServerRequest request) {
        request.setExpectMultipart(true);
        request.endHandler(new io.vertx.core.Handler<Void>() {

            @Override
            public void handle(Void v) {
                final String login = request.formAttributes().get("login");
                final String activationCode = request.formAttributes().get("activationCode");
                final String email = request.formAttributes().get("mail");
                final String phone = request.formAttributes().get("phone");
                final String theme = request.formAttributes().get("theme");
                String password = request.formAttributes().get("password");
                String confirmPassword = request.formAttributes().get("confirmPassword");
                if (config.getBoolean("cgu", true) && !"true".equals(request.formAttributes().get("acceptCGU"))) {
                    trace.info("Invalid cgu " + login);
                    JsonObject error = new JsonObject().put("error", new JsonObject().put("message", "invalid.cgu"))
                            .put("cgu", true);
                    if (activationCode != null) {
                        error.put("activationCode", activationCode);
                    }
                    if (login != null) {
                        error.put("login", login);
                    }
                    renderJson(request, error);
                } else if (login == null || activationCode == null || password == null || login.trim().isEmpty()
                        || activationCode.trim().isEmpty() || password.trim().isEmpty()
                        || !password.equals(confirmPassword) || !passwordPattern.matcher(password).matches()
                        || (config.getJsonObject("mandatory", new JsonObject()).getBoolean("mail", false)
                                && (email == null || email.trim().isEmpty() || invalidEmails.containsKey(email)))
                        || (config.getJsonObject("mandatory", new JsonObject()).getBoolean("phone", false)
                                && (phone == null || phone.trim().isEmpty()))
                        || (email != null && !email.trim().isEmpty() && !StringValidation.isEmail(email))
                        || (phone != null && !phone.trim().isEmpty() && !StringValidation.isPhone(phone))) {
                    trace.info("Echec de l'activation du compte utilisateur " + login);
                    JsonObject error = new JsonObject().put("error",
                            new JsonObject().put("message",
                                    I18n.getInstance().translate("auth.activation.invalid.argument",
                                            getHost(request), I18n.acceptLanguage(request))));
                    if (activationCode != null) {
                        error.put("activationCode", activationCode);
                    }
                    if (login != null) {
                        error.put("login", login);
                    }
                    if (config.getBoolean("cgu", true)) {
                        error.put("cgu", true);
                    }
                    renderJson(request, error);
                } else {
                    userAuthAccount.activateAccount(login, activationCode, password, email, phone, theme, request,
                            new io.vertx.core.Handler<Either<String, String>>() {

                                @Override
                                public void handle(Either<String, String> activated) {
                                    if (activated.isRight() && activated.right().getValue() != null) {
                                        handleActivation(login, request, activated);
                                    } else {
                                        // if failed because duplicated user
                                        if (activated.isLeft() && "activation.error.duplicated"
                                                .equals(activated.left().getValue())) {
                                            trace.info("Echec de l'activation : utilisateur " + login
                                                    + " en doublon.");
                                            JsonObject error = new JsonObject().put("error",
                                                    new JsonObject().put("message",
                                                            I18n.getInstance().translate(
                                                                    activated.left().getValue(), getHost(request),
                                                                    I18n.acceptLanguage(request))));
                                            error.put("activationCode", activationCode);
                                            renderJson(request, error);
                                        } else {
                                            // else try activation with loginAlias
                                            userAuthAccount.activateAccountByLoginAlias(login, activationCode,
                                                    password, email, phone, theme, request,
                                                    new io.vertx.core.Handler<Either<String, String>>() {
                                                        @Override
                                                        public void handle(Either<String, String> activated) {
                                                            if (activated.isRight()
                                                                    && activated.right().getValue() != null) {
                                                                handleActivation(login, request, activated);
                                                            } else {
                                                                trace.info(
                                                                        "Echec de l'activation : compte utilisateur "
                                                                                + login
                                                                                + " introuvable ou dj activ.");
                                                                JsonObject error = new JsonObject().put("error",
                                                                        new JsonObject().put("message",
                                                                                I18n.getInstance().translate(
                                                                                        activated.left().getValue(),
                                                                                        getHost(request),
                                                                                        I18n.acceptLanguage(
                                                                                                request))));
                                                                error.put("activationCode", activationCode);
                                                                renderJson(request, error);
                                                            }
                                                        }
                                                    });
                                        }
                                    }
                                }
                            });
                }
            }
        });
    }

    private void handleActivation(String login, HttpServerRequest request, Either<String, String> activated) {
        final String userId = activated.right().getValue();
        trace.info("Activation du compte utilisateur " + login);
        eventStore.createAndStoreEvent(AuthEvent.ACTIVATION.name(), login);
        if (config.getBoolean("activationAutoLogin", false)) {
            trace.info("Connexion de l'utilisateur " + login);
            userAuthAccount.storeDomain(userId, Renders.getHost(request), Renders.getScheme(request),
                    new io.vertx.core.Handler<Boolean>() {
                        public void handle(Boolean ok) {
                            if (!ok) {
                                trace.error("[Auth](loginSubmit) Error while storing last known domain for user "
                                        + userId);
                            }
                        }
                    });
            eventStore.createAndStoreEvent(AuthEvent.LOGIN.name(), login);
            createSession(userId, request, getScheme(request) + "://" + getHost(request));
        } else {
            redirect(request, "/auth/login");
        }
    }

    @Get("/forgot")
    public void forgotPassword(final HttpServerRequest request) {
        final JsonObject context = new JsonObject();
        UserUtils.getUserInfos(eb, request, new io.vertx.core.Handler<UserInfos>() {
            @Override
            public void handle(UserInfos user) {
                context.put("notLoggedIn", user == null);
                renderView(request, context);
            }
        });
    }

    @Get("/upgrade")
    public void upgrade(final HttpServerRequest request) {
        final JsonObject context = new JsonObject();
        UserUtils.getUserInfos(eb, request, new io.vertx.core.Handler<UserInfos>() {
            @Override
            public void handle(UserInfos user) {
                context.put("notLoggedIn", user == null);
                renderView(request, context);
            }
        });
    }

    @Post("/forgot-id")
    public void forgetId(final HttpServerRequest request) {
        RequestUtils.bodyToJson(request, new io.vertx.core.Handler<JsonObject>() {
            public void handle(JsonObject data) {
                final String mail = data.getString("mail");
                final String service = data.getString("service");
                final String firstName = data.getString("firstName");
                final String structure = data.getString("structureId");
                if (mail == null || mail.trim().isEmpty()) {
                    badRequest(request);
                    return;
                }
                userAuthAccount.findByMailAndFirstNameAndStructure(mail, firstName, structure,
                        new io.vertx.core.Handler<Either<String, JsonArray>>() {
                            @Override
                            public void handle(Either<String, JsonArray> event) {
                                //No user with that email, or more than one found.
                                if (event.isLeft()) {
                                    badRequest(request, event.left().getValue());
                                    return;
                                }
                                JsonArray results = event.right().getValue();
                                if (results.size() == 0) {
                                    badRequest(request, "no.match");
                                    return;
                                }
                                JsonArray structures = new fr.wseduc.webutils.collections.JsonArray();
                                if (results.size() > 1) {
                                    for (Object ob : results) {
                                        JsonObject j = (JsonObject) ob;
                                        j.remove("login");
                                        j.remove("mobile");
                                        if (!structures.toString().contains(j.getString("structureId")))
                                            structures.add(j);
                                    }
                                    if (firstName != null && structures.size() == 1)
                                        badRequest(request, "non.unique.result");
                                    else
                                        renderJson(request, new JsonObject().put("structures", structures));
                                    return;
                                }
                                JsonObject match = results.getJsonObject(0);
                                final String id = match.getString("login", "");
                                final String mobile = match.getString("mobile", "");

                                //Force mail
                                if ("mail".equals(service)) {
                                    userAuthAccount.sendForgottenIdMail(request, id, mail,
                                            new io.vertx.core.Handler<Either<String, JsonObject>>() {
                                                public void handle(Either<String, JsonObject> event) {
                                                    if (event.isLeft()) {
                                                        badRequest(request, event.left().getValue());
                                                        return;
                                                    }
                                                    if (smsProvider != null && !smsProvider.isEmpty()) {
                                                        final String obfuscatedMobile = StringValidation
                                                                .obfuscateMobile(mobile);
                                                        renderJson(request,
                                                                new JsonObject().put("mobile", obfuscatedMobile));
                                                    } else {
                                                        renderJson(request, new JsonObject());
                                                    }
                                                }
                                            });
                                } else if ("mobile".equals(service) && !mobile.isEmpty() && smsProvider != null
                                        && !smsProvider.isEmpty()) {
                                    eventStore.createAndStoreEvent(AuthEvent.SMS.name(), id);
                                    userAuthAccount.sendForgottenIdSms(request, id, mobile,
                                            DefaultResponseHandler.defaultResponseHandler(request));
                                } else {
                                    badRequest(request);
                                }
                            }
                        });
            }
        });
    }

    @Get("/password-channels")
    public void getForgotPasswordService(final HttpServerRequest request) {
        userAuthAccount.findByLogin(request.params().get("login"), null, checkFederatedLogin,
                new io.vertx.core.Handler<Either<String, JsonObject>>() {
                    public void handle(Either<String, JsonObject> result) {
                        if (result.isLeft()) {
                            badRequest(request, result.left().getValue());
                            return;
                        }
                        if (result.right().getValue().size() == 0) {
                            badRequest(request, "no.match");
                            return;
                        }

                        final String mail = result.right().getValue().getString("email", "");
                        final String mobile = result.right().getValue().getString("mobile", "");

                        boolean mailCheck = mail != null && !mail.trim().isEmpty();
                        boolean mobileCheck = mobile != null && !mobile.trim().isEmpty();

                        if (!mailCheck && !mobileCheck) {
                            badRequest(request, "no.match");
                            return;
                        }

                        final String obfuscatedMail = StringValidation.obfuscateMail(mail);
                        final String obfuscatedMobile = StringValidation.obfuscateMobile(mobile);

                        if (smsProvider != null && !smsProvider.isEmpty())
                            renderJson(request,
                                    new JsonObject().put("mobile", obfuscatedMobile).put("mail", obfuscatedMail));
                        else
                            renderJson(request, new JsonObject().put("mail", obfuscatedMail));
                    }
                });
    }

    @Post("/forgot-password")
    public void forgotPasswordSubmit(final HttpServerRequest request) {
        RequestUtils.bodyToJson(request, new io.vertx.core.Handler<JsonObject>() {
            public void handle(JsonObject data) {
                final String login = data.getString("login");
                final String service = data.getString("service");
                final String resetCode = StringValidation.generateRandomCode(8);
                if (login == null || login.trim().isEmpty() || service == null || service.trim().isEmpty()) {
                    badRequest(request, "invalid.login");
                    return;
                }

                userAuthAccount.findByLogin(login, resetCode, checkFederatedLogin,
                        new io.vertx.core.Handler<Either<String, JsonObject>>() {
                            public void handle(Either<String, JsonObject> result) {
                                if (result.isLeft()) {
                                    badRequest(request, result.left().getValue());
                                    return;
                                }
                                if (result.right().getValue().size() == 0) {
                                    badRequest(request, "no.match");
                                    return;
                                }

                                final String mail = result.right().getValue().getString("email", "");
                                final String mobile = result.right().getValue().getString("mobile", "");

                                if ("mail".equals(service)) {
                                    userAuthAccount.sendResetPasswordMail(request, mail, resetCode,
                                            DefaultResponseHandler.defaultResponseHandler(request));
                                } else if ("mobile".equals(service) && smsProvider != null
                                        && !smsProvider.isEmpty()) {
                                    eventStore.createAndStoreEvent(AuthEvent.SMS.name(), login);
                                    userAuthAccount.sendResetPasswordSms(request, mobile, resetCode,
                                            DefaultResponseHandler.defaultResponseHandler(request));
                                } else {
                                    badRequest(request, "invalid.service");
                                }
                            }
                        });

            }
        });
    }

    @Post("/sendResetPassword")
    @SecuredAction(value = "", type = ActionType.RESOURCE)
    @IgnoreCsrf
    public void sendResetPassword(final HttpServerRequest request) {
        String login = request.formAttributes().get("login");
        String email = request.formAttributes().get("email");
        String mobile = request.formAttributes().get("mobile");
        SendPasswordDestination dest = null;

        if (login == null || login.trim().isEmpty()) {
            badRequest(request, "login required");
            return;
        }
        if (StringValidation.isEmail(email)) {
            dest = new SendPasswordDestination();
            dest.setType("email");
            dest.setValue(email);
        } else if (StringValidation.isPhone(mobile)) {
            dest = new SendPasswordDestination();
            dest.setType("mobile");
            dest.setValue(mobile);
        } else {
            badRequest(request, "valid email or valid mobile required");
            return;
        }

        userAuthAccount.sendResetCode(request, login, dest, checkFederatedLogin,
                new io.vertx.core.Handler<Boolean>() {
                    @Override
                    public void handle(Boolean sent) {
                        if (Boolean.TRUE.equals(sent)) {
                            renderJson(request, new JsonObject());
                        } else {
                            badRequest(request);
                        }
                    }
                });
    }

    @Put("/block/:userId")
    @SecuredAction(value = "", type = ActionType.RESOURCE)
    public void blockUser(final HttpServerRequest request) {
        RequestUtils.bodyToJson(request, new io.vertx.core.Handler<JsonObject>() {
            @Override
            public void handle(JsonObject json) {
                final String userId = request.params().get("userId");
                boolean block = json.getBoolean("block", true);
                userAuthAccount.blockUser(userId, block, new io.vertx.core.Handler<Boolean>() {
                    @Override
                    public void handle(Boolean r) {
                        if (Boolean.TRUE.equals(r)) {
                            request.response().end();
                            UserUtils.deletePermanentSession(eb, userId, null,
                                    new io.vertx.core.Handler<Boolean>() {
                                        @Override
                                        public void handle(Boolean event) {
                                            if (!event) {
                                                log.error("Error delete permanent session with userId : " + userId);
                                            }
                                        }
                                    });
                            UserUtils.deleteCacheSession(eb, userId, new io.vertx.core.Handler<Boolean>() {
                                @Override
                                public void handle(Boolean event) {
                                    if (!event) {
                                        log.error("Error delete cache session with userId : " + userId);
                                    }
                                }
                            });
                        } else {
                            badRequest(request);
                        }
                    }
                });
            }
        });
    }

    @Get("/reset/:resetCode")
    public void resetPassword(final HttpServerRequest request) {
        resetPasswordView(request, null);
    }

    private void resetPasswordView(final HttpServerRequest request, final JsonObject p) {
        UserUtils.getUserInfos(eb, request, new io.vertx.core.Handler<UserInfos>() {
            @Override
            public void handle(UserInfos user) {
                JsonObject params = p;
                if (params == null) {
                    params = new JsonObject();
                }
                if (user != null && "password".equals(request.params().get("resetCode"))) {
                    renderView(request,
                            params.put("login", user.getLogin()).put("callback", "/userbook/mon-compte"),
                            "changePassword.html", null);
                } else {
                    renderView(request,
                            params.put("notLoggedIn", user == null).put("login", request.params().get("login"))
                                    .put("resetCode", request.params().get("resetCode")),
                            "reset.html", null);
                }
            }
        });
    }

    @Post("/reset")
    public void resetPasswordSubmit(final HttpServerRequest request) {
        request.setExpectMultipart(true);
        request.endHandler(new io.vertx.core.Handler<Void>() {

            @Override
            public void handle(Void v) {
                final String login = request.formAttributes().get("login");
                final String resetCode = request.formAttributes().get("resetCode");
                final String oldPassword = request.formAttributes().get("oldPassword");
                final String password = request.formAttributes().get("password");
                String confirmPassword = request.formAttributes().get("confirmPassword");
                final String callback = Utils.getOrElse(request.formAttributes().get("callback"), "/auth/login",
                        false);
                if (login == null
                        || ((resetCode == null || resetCode.trim().isEmpty())
                                && (oldPassword == null || oldPassword.trim().isEmpty()))
                        || password == null || login.trim().isEmpty() || password.trim().isEmpty()
                        || !password.equals(confirmPassword) || !passwordPattern.matcher(password).matches()) {
                    trace.info(
                            "Erreur lors de la rinitialisation " + "du mot de passe de l'utilisateur " + login);
                    JsonObject error = new JsonObject().put("error",
                            new JsonObject().put("message",
                                    I18n.getInstance().translate("auth.reset.invalid.argument", getHost(request),
                                            I18n.acceptLanguage(request))));
                    if (resetCode != null) {
                        error.put("resetCode", resetCode);
                    }
                    renderJson(request, error);
                } else {
                    final io.vertx.core.Handler<Boolean> resultHandler = new io.vertx.core.Handler<Boolean>() {

                        @Override
                        public void handle(Boolean reseted) {
                            if (Boolean.TRUE.equals(reseted)) {
                                trace.info("Rinitialisation russie du mot de passe de l'utilisateur " + login);
                                redirect(request, callback);
                            } else {
                                trace.info("Erreur lors de la rinitialisation "
                                        + "du mot de passe de l'utilisateur " + login);
                                error(request, resetCode);
                            }
                        }
                    };
                    if (resetCode != null && !resetCode.trim().isEmpty()) {
                        userAuthAccount.resetPassword(login, resetCode, password, resultHandler);
                    } else {
                        DataHandler data = oauthDataFactory.create(new HttpServerRequestAdapter(request));
                        data.getUserId(login, oldPassword, new Handler<String>() {

                            @Override
                            public void handle(String userId) {
                                if (userId != null && !userId.trim().isEmpty()) {
                                    userAuthAccount.changePassword(login, password, resultHandler);
                                } else {
                                    error(request, null);
                                }
                            }
                        });
                    }
                }
            }

            private void error(final HttpServerRequest request, final String resetCode) {
                JsonObject error = new JsonObject().put("error", new JsonObject().put("message", I18n.getInstance()
                        .translate("reset.error", getHost(request), I18n.acceptLanguage(request))));
                if (resetCode != null) {
                    error.put("resetCode", resetCode);
                }
                renderJson(request, error);
            }
        });
    }

    @Get("/cgu")
    public void cgu(final HttpServerRequest request) {
        final JsonObject context = new JsonObject();
        UserUtils.getUserInfos(eb, request, new io.vertx.core.Handler<UserInfos>() {
            @Override
            public void handle(UserInfos user) {
                context.put("notLoggedIn", user == null);
                renderView(request, context);
            }
        });
    }

    @Delete("/sessions")
    @SecuredAction(value = "", type = ActionType.AUTHENTICATED)
    public void deletePermanentSessions(final HttpServerRequest request) {
        UserUtils.getUserInfos(eb, request, new io.vertx.core.Handler<UserInfos>() {
            @Override
            public void handle(UserInfos user) {
                if (user != null) {
                    String sessionId = CookieHelper.getInstance().getSigned("oneSessionId", request);
                    UserUtils.deletePermanentSession(eb, user.getUserId(), sessionId,
                            new io.vertx.core.Handler<Boolean>() {
                                @Override
                                public void handle(Boolean event) {
                                    if (event) {
                                        ok(request);
                                    } else {
                                        renderError(request);
                                    }
                                }
                            });
                } else {
                    unauthorized(request);
                }
            }
        });
    }

    public void setUserAuthAccount(UserAuthAccount userAuthAccount) {
        this.userAuthAccount = userAuthAccount;
    }

    public void setEventStore(EventStore eventStore) {
        this.eventStore = eventStore;
    }

    public void setAuthorizedHostsLogin(JsonArray authorizedHostsLogin) {
        this.authorizedHostsLogin = authorizedHostsLogin;
    }

}