All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sonar.server.authentication.JwtHttpHandler Maven / Gradle / Ivy

There is a newer version: 7.2.1
Show newest version
/*
 * SonarQube
 * Copyright (C) 2009-2016 SonarSource SA
 * mailto:contact AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package org.sonar.server.authentication;

import com.google.common.collect.ImmutableMap;
import io.jsonwebtoken.Claims;
import java.util.Date;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.time.DateUtils;
import org.sonar.api.config.Settings;
import org.sonar.api.server.ServerSide;
import org.sonar.api.utils.System2;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.user.UserDto;

import static java.util.Objects.requireNonNull;
import static org.elasticsearch.common.Strings.isNullOrEmpty;
import static org.sonar.server.authentication.CookieUtils.findCookie;

@ServerSide
public class JwtHttpHandler {

  private static final String SESSION_TIMEOUT_PROPERTY = "sonar.auth.sessionTimeoutInHours";
  private static final int SESSION_TIMEOUT_DEFAULT_VALUE_IN_SECONDS = 3 * 24 * 60 * 60;

  private static final String JWT_COOKIE = "JWT-SESSION";
  private static final String LAST_REFRESH_TIME_PARAM = "lastRefreshTime";

  private static final String CSRF_JWT_PARAM = "xsrfToken";

  // Time after which a user will be disconnected
  private static final int SESSION_DISCONNECT_IN_SECONDS = 3 * 30 * 24 * 60 * 60;

  // This refresh time is used to refresh the session
  // The value must be lower than sessionTimeoutInSeconds
  private static final int SESSION_REFRESH_IN_SECONDS = 5 * 60;

  private final System2 system2;
  private final DbClient dbClient;
  private final JwtSerializer jwtSerializer;

  // This timeout is used to disconnect the user we he has not browse any page for a while
  private final int sessionTimeoutInSeconds;
  private final JwtCsrfVerifier jwtCsrfVerifier;

  public JwtHttpHandler(System2 system2, DbClient dbClient, Settings settings, JwtSerializer jwtSerializer, JwtCsrfVerifier jwtCsrfVerifier) {
    this.jwtSerializer = jwtSerializer;
    this.dbClient = dbClient;
    this.system2 = system2;
    this.sessionTimeoutInSeconds = getSessionTimeoutInSeconds(settings);
    this.jwtCsrfVerifier = jwtCsrfVerifier;
  }

  public void generateToken(UserDto user, HttpServletRequest request, HttpServletResponse response) {
    String csrfState = jwtCsrfVerifier.generateState(request, response, sessionTimeoutInSeconds);

    String token = jwtSerializer.encode(new JwtSerializer.JwtSession(
      user.getLogin(),
      sessionTimeoutInSeconds,
      ImmutableMap.of(
        LAST_REFRESH_TIME_PARAM, system2.now(),
        CSRF_JWT_PARAM, csrfState)));
    response.addCookie(createCookie(request, JWT_COOKIE, token, sessionTimeoutInSeconds));
  }

  public Optional validateToken(HttpServletRequest request, HttpServletResponse response) {
    Optional userDto = validate(request, response);
    if (userDto.isPresent()) {
      return userDto;
    }
    return Optional.empty();
  }

  private Optional validate(HttpServletRequest request, HttpServletResponse response) {
    Optional token = getTokenFromCookie(request);
    if (!token.isPresent()) {
      return Optional.empty();
    }
    return validateToken(token.get(), request, response);
  }

  private static Optional getTokenFromCookie(HttpServletRequest request) {
    Optional jwtCookie = findCookie(JWT_COOKIE, request);
    if (!jwtCookie.isPresent()) {
      return Optional.empty();
    }
    Cookie cookie = jwtCookie.get();
    String token = cookie.getValue();
    if (isNullOrEmpty(token)) {
      return Optional.empty();
    }
    return Optional.of(token);
  }

  private Optional validateToken(String tokenEncoded, HttpServletRequest request, HttpServletResponse response) {
    Optional claims = jwtSerializer.decode(tokenEncoded);
    if (!claims.isPresent()) {
      return Optional.empty();
    }

    Date now = new Date(system2.now());
    Claims token = claims.get();
    if (now.after(DateUtils.addSeconds(token.getIssuedAt(), SESSION_DISCONNECT_IN_SECONDS))) {
      return Optional.empty();
    }
    jwtCsrfVerifier.verifyState(request, (String) token.get(CSRF_JWT_PARAM));

    if (now.after(DateUtils.addSeconds(getLastRefreshDate(token), SESSION_REFRESH_IN_SECONDS))) {
      refreshToken(token, request, response);
    }

    Optional user = selectUserFromDb(token.getSubject());
    if (!user.isPresent()) {
      return Optional.empty();
    }
    return Optional.of(user.get());
  }

  private static Date getLastRefreshDate(Claims token) {
    Long lastFreshTime = (Long) token.get(LAST_REFRESH_TIME_PARAM);
    requireNonNull(lastFreshTime, "last refresh time is missing in token");
    return new Date(lastFreshTime);
  }

  private void refreshToken(Claims token, HttpServletRequest request, HttpServletResponse response) {
    String refreshToken = jwtSerializer.refresh(token, sessionTimeoutInSeconds);
    response.addCookie(createCookie(request, JWT_COOKIE, refreshToken, sessionTimeoutInSeconds));
    jwtCsrfVerifier.refreshState(request, response, (String) token.get(CSRF_JWT_PARAM), sessionTimeoutInSeconds);
  }

  void removeToken(HttpServletRequest request, HttpServletResponse response) {
    response.addCookie(createCookie(request, JWT_COOKIE, null, 0));
    jwtCsrfVerifier.removeState(request, response);
  }

  private static Cookie createCookie(HttpServletRequest request, String name, @Nullable String value, int expirationInSeconds) {
    return CookieUtils.createCookie(name, value, true, expirationInSeconds, request);
  }

  private Optional selectUserFromDb(String userLogin) {
    DbSession dbSession = dbClient.openSession(false);
    try {
      return Optional.ofNullable(dbClient.userDao().selectActiveUserByLogin(dbSession, userLogin));
    } finally {
      dbClient.closeSession(dbSession);
    }
  }

  private static int getSessionTimeoutInSeconds(Settings settings) {
    int propertyFromSettings = settings.getInt(SESSION_TIMEOUT_PROPERTY);
    if (propertyFromSettings > 0) {
      return propertyFromSettings * 60 * 60;
    }
    return SESSION_TIMEOUT_DEFAULT_VALUE_IN_SECONDS;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy