compileOnly 'com.google.code.findbugs:jsr305'
compileOnly 'com.squareup.okhttp3:okhttp'
compileOnly 'javax.servlet:javax.servlet-api'
+ compileOnly project(':server:sonar-db-dao')
compileOnly project(':sonar-core')
testCompile 'com.tngtech.java:junit-dataprovider'
testCompile 'junit:junit'
testCompile 'org.assertj:assertj-core'
testCompile 'org.mockito:mockito-core'
+ testCompile testFixtures(project(':server:sonar-db-dao'))
}
private static final String STATE_REQUEST_PARAMETER = "RelayState";
private final SamlSettings samlSettings;
+ private final SamlMessageIdChecker samlMessageIdChecker;
- public SamlIdentityProvider(SamlSettings samlSettings) {
+ public SamlIdentityProvider(SamlSettings samlSettings, SamlMessageIdChecker samlMessageIdChecker) {
this.samlSettings = samlSettings;
+ this.samlMessageIdChecker = samlMessageIdChecker;
}
@Override
LOGGER.trace("Name ID : {}", auth.getNameId());
checkAuthentication(auth);
+ samlMessageIdChecker.check(auth);
LOGGER.trace("Attributes received : {}", auth.getAttributes());
String login = getNonNullFirstAttribute(auth, samlSettings.getUserLogin());
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info 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.auth.saml;
+
+import com.onelogin.saml2.Auth;
+import org.joda.time.Instant;
+import org.sonar.api.server.ServerSide;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.user.SamlMessageIdDto;
+
+import static java.util.Objects.requireNonNull;
+
+@ServerSide
+public class SamlMessageIdChecker {
+
+ private final DbClient dbClient;
+
+ public SamlMessageIdChecker(DbClient dbClient) {
+ this.dbClient = dbClient;
+ }
+
+ public void check(Auth auth) {
+ String messageId = requireNonNull(auth.getLastMessageId(), "Message ID is missing");
+ Instant lastAssertionNotOnOrAfter = auth.getLastAssertionNotOnOrAfter().stream()
+ .sorted()
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException("Missing NotOnOrAfter element"));
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ dbClient.samlMessageIdDao().selectByMessageId(dbSession, messageId)
+ .ifPresent(m -> {
+ throw new IllegalArgumentException("This message has already been processed");
+ });
+ dbClient.samlMessageIdDao().insert(dbSession, new SamlMessageIdDto()
+ .setMessageId(messageId)
+ .setExpirationDate(lastAssertionNotOnOrAfter.getMillis()));
+ dbSession.commit();
+ }
+ }
+
+}
protected void configureModule() {
add(
SamlIdentityProvider.class,
+ SamlMessageIdChecker.class,
SamlSettings.class);
List<PropertyDefinition> definitions = SamlSettings.definitions();
add(definitions.toArray(new Object[definitions.size()]));
import org.apache.commons.io.IOUtils;
import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.ExpectedException;
import org.sonar.api.config.PropertyDefinitions;
import org.sonar.api.config.internal.MapSettings;
import org.sonar.api.server.authentication.OAuth2IdentityProvider;
import org.sonar.api.server.authentication.UnauthorizedException;
import org.sonar.api.server.authentication.UserIdentity;
import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
public class SamlIdentityProviderTest {
@Rule
- public ExpectedException expectedException = ExpectedException.none();
+ public DbTester db = DbTester.create();
private MapSettings settings = new MapSettings(new PropertyDefinitions(System2.INSTANCE, SamlSettings.definitions()));
- private SamlIdentityProvider underTest = new SamlIdentityProvider(new SamlSettings(settings.asConfig()));
+ private SamlMessageIdChecker samlMessageIdChecker = mock(SamlMessageIdChecker.class);
+
+ private SamlIdentityProvider underTest = new SamlIdentityProvider(new SamlSettings(settings.asConfig()), new SamlMessageIdChecker(db.getDbClient()));
@Test
public void check_fields() {
settings.setProperty("sonar.auth.saml.loginUrl", "invalid");
DumbInitContext context = new DumbInitContext();
- expectedException.expect(IllegalStateException.class);
- expectedException.expectMessage("Fail to create Auth");
-
- underTest.init(context);
+ assertThatThrownBy(() -> underTest.init(context))
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("Fail to create Auth");
}
@Test
setSettings(true);
DumbCallbackContext callbackContext = new DumbCallbackContext("encoded_response_without_login.txt");
- expectedException.expect(NullPointerException.class);
- expectedException.expectMessage("login is missing");
+ assertThatThrownBy(() -> underTest.callback(callbackContext))
+ .isInstanceOf(NullPointerException.class)
+ .hasMessage("login is missing");
- underTest.callback(callbackContext);
}
@Test
setSettings(true);
DumbCallbackContext callbackContext = new DumbCallbackContext("encoded_response_without_name.txt");
- expectedException.expect(NullPointerException.class);
- expectedException.expectMessage("name is missing");
-
- underTest.callback(callbackContext);
+ assertThatThrownBy(() -> underTest.callback(callbackContext))
+ .isInstanceOf(NullPointerException.class)
+ .hasMessage("name is missing");
}
@Test
settings.setProperty("sonar.auth.saml.certificate.secured", "invalid");
DumbCallbackContext callbackContext = new DumbCallbackContext("encoded_full_response.txt");
- expectedException.expect(IllegalStateException.class);
- expectedException.expectMessage("Fail to create Auth");
-
- underTest.callback(callbackContext);
+ assertThatThrownBy(() -> underTest.callback(callbackContext))
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("Fail to create Auth");
}
@Test
"-----END CERTIFICATE-----\n");
DumbCallbackContext callbackContext = new DumbCallbackContext("encoded_full_response.txt");
- expectedException.expect(UnauthorizedException.class);
- expectedException.expectMessage("Signature validation failed. SAML Response rejected");
+ assertThatThrownBy(() -> underTest.callback(callbackContext))
+ .isInstanceOf(UnauthorizedException.class)
+ .hasMessage("Signature validation failed. SAML Response rejected");
+ }
+
+ @Test
+ public void fail_callback_when_message_was_already_sent() {
+ setSettings(true);
+ DumbCallbackContext callbackContext = new DumbCallbackContext("encoded_minimal_response.txt");
underTest.callback(callbackContext);
+
+ assertThatThrownBy(() -> underTest.callback(callbackContext))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("This message has already been processed");
}
private void setSettings(boolean enabled) {
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info 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.auth.saml;
+
+import com.google.common.collect.ImmutableList;
+import com.onelogin.saml2.Auth;
+import java.util.Arrays;
+import org.joda.time.Instant;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.user.SamlMessageIdDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class SamlMessageIdCheckerTest {
+
+ @Rule
+ public DbTester db = DbTester.create();
+
+ private DbSession dbSession = db.getSession();
+
+ private Auth auth = mock(Auth.class);
+
+ private SamlMessageIdChecker underTest = new SamlMessageIdChecker(db.getDbClient());
+
+ @Test
+ public void check_do_not_fail_when_message_id_is_new_and_insert_saml_message_in_db() {
+ db.getDbClient().samlMessageIdDao().insert(dbSession, new SamlMessageIdDto().setMessageId("MESSAGE_1").setExpirationDate(1_000_000_000L));
+ db.commit();
+ when(auth.getLastMessageId()).thenReturn("MESSAGE_2");
+ when(auth.getLastAssertionNotOnOrAfter()).thenReturn(ImmutableList.of(Instant.ofEpochMilli(10_000_000_000L)));
+
+ assertThatCode(() -> underTest.check(auth)).doesNotThrowAnyException();
+
+ SamlMessageIdDto result = db.getDbClient().samlMessageIdDao().selectByMessageId(dbSession, "MESSAGE_2").get();
+ assertThat(result.getMessageId()).isEqualTo("MESSAGE_2");
+ assertThat(result.getExpirationDate()).isEqualTo(10_000_000_000L);
+ }
+
+ @Test
+ public void check_fails_when_message_id_already_exist() {
+ db.getDbClient().samlMessageIdDao().insert(dbSession, new SamlMessageIdDto().setMessageId("MESSAGE_1").setExpirationDate(1_000_000_000L));
+ db.commit();
+ when(auth.getLastMessageId()).thenReturn("MESSAGE_1");
+ when(auth.getLastAssertionNotOnOrAfter()).thenReturn(ImmutableList.of(Instant.ofEpochMilli(10_000_000_000L)));
+
+ assertThatThrownBy(() -> underTest.check(auth))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("This message has already been processed");
+ }
+
+ @Test
+ public void check_insert_message_id_using_oldest_NotOnOrAfter_value() {
+ db.getDbClient().samlMessageIdDao().insert(dbSession, new SamlMessageIdDto().setMessageId("MESSAGE_1").setExpirationDate(1_000_000_000L));
+ db.commit();
+ when(auth.getLastMessageId()).thenReturn("MESSAGE_2");
+ when(auth.getLastAssertionNotOnOrAfter())
+ .thenReturn(Arrays.asList(Instant.ofEpochMilli(10_000_000_000L), Instant.ofEpochMilli(30_000_000_000L), Instant.ofEpochMilli(20_000_000_000L)));
+
+ assertThatCode(() -> underTest.check(auth)).doesNotThrowAnyException();
+
+ SamlMessageIdDto result = db.getDbClient().samlMessageIdDao().selectByMessageId(dbSession, "MESSAGE_2").get();
+ assertThat(result.getMessageId()).isEqualTo("MESSAGE_2");
+ assertThat(result.getExpirationDate()).isEqualTo(10_000_000_000L);
+ }
+}
public void verify_count_of_added_components() {
ComponentContainer container = new ComponentContainer();
new SamlModule().configure(container);
- assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 12);
+ assertThat(container.size()).isGreaterThan(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER);
}
}
"qprofile_edit_users",
"quality_gates",
"quality_gate_conditions",
+ "saml_message_ids",
"rules",
"rules_metadata",
"rules_parameters",
import org.sonar.db.user.GroupDao;
import org.sonar.db.user.GroupMembershipDao;
import org.sonar.db.user.RoleDao;
+import org.sonar.db.user.SamlMessageIdDao;
import org.sonar.db.user.SessionTokensDao;
import org.sonar.db.user.UserDao;
import org.sonar.db.user.UserGroupDao;
RoleDao.class,
RuleDao.class,
RuleRepositoryDao.class,
+ SamlMessageIdDao.class,
SnapshotDao.class,
SchemaMigrationDao.class,
SessionTokensDao.class,
import org.sonar.db.user.GroupDao;
import org.sonar.db.user.GroupMembershipDao;
import org.sonar.db.user.RoleDao;
+import org.sonar.db.user.SamlMessageIdDao;
import org.sonar.db.user.SessionTokensDao;
import org.sonar.db.user.UserDao;
import org.sonar.db.user.UserGroupDao;
private final NewCodePeriodDao newCodePeriodDao;
private final ProjectDao projectDao;
private final SessionTokensDao sessionTokensDao;
+ private final SamlMessageIdDao samlMessageIdDao;
public DbClient(Database database, MyBatis myBatis, DBSessions dbSessions, Dao... daos) {
this.database = database;
newCodePeriodDao = getDao(map, NewCodePeriodDao.class);
projectDao = getDao(map, ProjectDao.class);
sessionTokensDao = getDao(map, SessionTokensDao.class);
+ samlMessageIdDao = getDao(map, SamlMessageIdDao.class);
}
public DbSession openSession(boolean batch) {
return sessionTokensDao;
}
+ public SamlMessageIdDao samlMessageIdDao() {
+ return samlMessageIdDao;
+ }
+
}
import org.sonar.db.user.GroupMembershipDto;
import org.sonar.db.user.GroupMembershipMapper;
import org.sonar.db.user.RoleMapper;
+import org.sonar.db.user.SamlMessageIdMapper;
import org.sonar.db.user.SessionTokenMapper;
import org.sonar.db.user.UserDto;
import org.sonar.db.user.UserGroupDto;
RoleMapper.class,
RuleMapper.class,
RuleRepositoryMapper.class,
+ SamlMessageIdMapper.class,
SchemaMigrationMapper.class,
SessionTokenMapper.class,
SnapshotMapper.class,
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info 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.db.user;
+
+import java.util.Optional;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.db.Dao;
+import org.sonar.db.DbSession;
+
+public class SamlMessageIdDao implements Dao {
+
+ private final System2 system2;
+ private final UuidFactory uuidFactory;
+
+ public SamlMessageIdDao(System2 system2, UuidFactory uuidFactory) {
+ this.system2 = system2;
+ this.uuidFactory = uuidFactory;
+ }
+
+ public Optional<SamlMessageIdDto> selectByMessageId(DbSession session, String messageId) {
+ return Optional.ofNullable(mapper(session).selectByMessageId(messageId));
+ }
+
+ public SamlMessageIdDto insert(DbSession session, SamlMessageIdDto dto) {
+ long now = system2.now();
+ mapper(session).insert(dto
+ .setUuid(uuidFactory.create())
+ .setCreatedAt(now));
+ return dto;
+ }
+
+ public int deleteExpired(DbSession dbSession) {
+ return mapper(dbSession).deleteExpired(system2.now());
+ }
+
+ private static SamlMessageIdMapper mapper(DbSession session) {
+ return session.getMapper(SamlMessageIdMapper.class);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info 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.db.user;
+
+public class SamlMessageIdDto {
+
+ private String uuid;
+
+ /**
+ * Message ID from the SAML response received during authentication.
+ */
+ private String messageId;
+
+ /**
+ * Expiration date is coming from the NotOnOrAfter attribute of the SAML response.
+ *
+ * A row that contained an expired date can be safely deleted from database.
+ */
+ private long expirationDate;
+
+ private long createdAt;
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ SamlMessageIdDto setUuid(String uuid) {
+ this.uuid = uuid;
+ return this;
+ }
+
+ public String getMessageId() {
+ return messageId;
+ }
+
+ public SamlMessageIdDto setMessageId(String messageId) {
+ this.messageId = messageId;
+ return this;
+ }
+
+ public long getExpirationDate() {
+ return expirationDate;
+ }
+
+ public SamlMessageIdDto setExpirationDate(long expirationDate) {
+ this.expirationDate = expirationDate;
+ return this;
+ }
+
+ public long getCreatedAt() {
+ return createdAt;
+ }
+
+ SamlMessageIdDto setCreatedAt(long createdAt) {
+ this.createdAt = createdAt;
+ return this;
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info 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.db.user;
+
+import javax.annotation.CheckForNull;
+import org.apache.ibatis.annotations.Param;
+
+public interface SamlMessageIdMapper {
+
+ @CheckForNull
+ SamlMessageIdDto selectByMessageId(String messageId);
+
+ void insert(@Param("dto") SamlMessageIdDto dto);
+
+ int deleteExpired(@Param("now") long now);
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.user.SamlMessageIdMapper">
+
+ <sql id="columns">
+ smi.uuid as uuid,
+ smi.message_id as "messageId",
+ smi.expiration_date as "expirationDate",
+ smi.created_at as "createdAt"
+ </sql>
+
+ <select id="selectByMessageId" parameterType="String" resultType="org.sonar.db.user.SamlMessageIdDto">
+ select
+ <include refid="columns"/>
+ from saml_message_ids smi
+ where smi.message_id=#{messageId, jdbcType=VARCHAR}
+ </select>
+
+ <insert id="insert" parameterType="Map" useGeneratedKeys="false">
+ insert into saml_message_ids
+ (
+ uuid,
+ message_id,
+ expiration_date,
+ created_at
+ )
+ values (
+ #{dto.uuid, jdbcType=VARCHAR},
+ #{dto.messageId, jdbcType=VARCHAR},
+ #{dto.expirationDate, jdbcType=BIGINT},
+ #{dto.createdAt, jdbcType=BIGINT}
+ )
+ </insert>
+
+ <delete id="deleteExpired" parameterType="Long" >
+ delete from saml_message_ids where expiration_date < #{now, jdbcType=BIGINT}
+ </delete>
+
+</mapper>
);
ALTER TABLE "RULES_PROFILES" ADD CONSTRAINT "PK_RULES_PROFILES" PRIMARY KEY("UUID");
+CREATE TABLE "SAML_MESSAGE_IDS"(
+ "UUID" VARCHAR(40) NOT NULL,
+ "MESSAGE_ID" VARCHAR(255) NOT NULL,
+ "EXPIRATION_DATE" BIGINT NOT NULL,
+ "CREATED_AT" BIGINT NOT NULL
+);
+ALTER TABLE "SAML_MESSAGE_IDS" ADD CONSTRAINT "PK_SAML_MESSAGE_IDS" PRIMARY KEY("UUID");
+CREATE UNIQUE INDEX "SAML_MESSAGE_IDS_UNIQUE" ON "SAML_MESSAGE_IDS"("MESSAGE_ID");
+
CREATE TABLE "SESSION_TOKENS"(
"UUID" VARCHAR(40) NOT NULL,
"USER_UUID" VARCHAR(255) NOT NULL,
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info 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.db.user;
+
+import java.util.Optional;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.impl.utils.TestSystem2;
+import org.sonar.core.util.SequenceUuidFactory;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SamlMessageIdDaoTest {
+
+ private static final long NOW = 1_000_000_000L;
+
+ private TestSystem2 system2 = new TestSystem2().setNow(NOW);
+ @Rule
+ public DbTester db = DbTester.create(system2);
+
+ private DbSession dbSession = db.getSession();
+ private UuidFactory uuidFactory = new SequenceUuidFactory();
+
+ private SamlMessageIdDao underTest = new SamlMessageIdDao(system2, uuidFactory);
+
+ @Test
+ public void selectByMessageId() {
+ SamlMessageIdDto dto = new SamlMessageIdDto()
+ .setMessageId("ABCD")
+ .setExpirationDate(15_000_000_000L);
+ underTest.insert(dbSession, dto);
+
+ Optional<SamlMessageIdDto> result = underTest.selectByMessageId(dbSession, dto.getMessageId());
+
+ assertThat(result).isPresent();
+ assertThat(result.get().getMessageId()).isEqualTo("ABCD");
+ assertThat(result.get().getExpirationDate()).isEqualTo(15_000_000_000L);
+ assertThat(result.get().getCreatedAt()).isEqualTo(NOW);
+ }
+
+ @Test
+ public void uuid_created_at_and_updated_at_are_ignored_during_insert() {
+ SamlMessageIdDto dto = new SamlMessageIdDto()
+ .setMessageId("ABCD")
+ .setExpirationDate(15_000_000_000L)
+ // Following fields should be ignored
+ .setUuid("SHOULD_NOT_BE_USED")
+ .setCreatedAt(8_000_000_000L);
+ underTest.insert(dbSession, dto);
+
+ Optional<SamlMessageIdDto> result = underTest.selectByMessageId(dbSession, dto.getMessageId());
+
+ assertThat(result).isPresent();
+ assertThat(result.get().getUuid()).isNotEqualTo("SHOULD_NOT_BE_USED");
+ assertThat(result.get().getCreatedAt()).isEqualTo(NOW);
+ }
+
+ @Test
+ public void deleteExpired() {
+ SamlMessageIdDto expiredSamlMessageId1 = underTest.insert(dbSession, new SamlMessageIdDto()
+ .setMessageId("MESSAGE_1")
+ .setExpirationDate(NOW - 2_000_000_000L));
+ SamlMessageIdDto expiredSamlMessageId2 = underTest.insert(dbSession, new SamlMessageIdDto()
+ .setMessageId("MESSAGE_2")
+ .setExpirationDate(NOW - 2_000_000_000L));
+ SamlMessageIdDto validSamlMessageId = underTest.insert(dbSession, new SamlMessageIdDto()
+ .setMessageId("MESSAGE_3")
+ .setExpirationDate(NOW + 1_000_000_000L));
+
+ int result = underTest.deleteExpired(dbSession);
+
+ assertThat(underTest.selectByMessageId(dbSession, expiredSamlMessageId1.getMessageId())).isNotPresent();
+ assertThat(underTest.selectByMessageId(dbSession, expiredSamlMessageId2.getMessageId())).isNotPresent();
+ assertThat(underTest.selectByMessageId(dbSession, validSamlMessageId.getMessageId())).isPresent();
+ assertThat(result).isEqualTo(2);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info 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.platform.db.migration.version.v84;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.def.VarcharColumnDef;
+import org.sonar.server.platform.db.migration.sql.CreateIndexBuilder;
+import org.sonar.server.platform.db.migration.sql.CreateTableBuilder;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+import static org.sonar.server.platform.db.migration.def.BigIntegerColumnDef.newBigIntegerColumnDefBuilder;
+import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.UUID_SIZE;
+
+public class CreateSamlMessageIdsTable extends DdlChange {
+
+ private static final String TABLE_NAME = "saml_message_ids";
+ private static final VarcharColumnDef MESSAGE_ID_COLUMN = VarcharColumnDef.newVarcharColumnDefBuilder()
+ .setColumnName("message_id")
+ .setLimit(255)
+ .setIsNullable(false)
+ .build();
+
+ public CreateSamlMessageIdsTable(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ context.execute(new CreateTableBuilder(getDialect(), TABLE_NAME)
+ .addPkColumn(VarcharColumnDef.newVarcharColumnDefBuilder()
+ .setColumnName("uuid")
+ .setLimit(UUID_SIZE)
+ .setIsNullable(false)
+ .build())
+ .addColumn(MESSAGE_ID_COLUMN)
+ .addColumn(newBigIntegerColumnDefBuilder()
+ .setColumnName("expiration_date")
+ .setIsNullable(false)
+ .build())
+ .addColumn(newBigIntegerColumnDefBuilder()
+ .setColumnName("created_at")
+ .setIsNullable(false)
+ .build())
+ .build());
+
+ context.execute(new CreateIndexBuilder()
+ .setTable(TABLE_NAME)
+ .setName("saml_message_ids_unique")
+ .addColumn(MESSAGE_ID_COLUMN)
+ .setUnique(true)
+ .build());
+ }
+}
.add(3800, "Remove favourites for components with qualifiers 'DIR', 'FIL', 'UTS'", RemoveFilesFavouritesFromProperties.class)
.add(3801, "Create 'SESSION_TOKENS' table", CreateSessionTokensTable.class)
+ .add(3802, "Create 'SAML_MESSAGE_IDS' table", CreateSamlMessageIdsTable.class)
;
}
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info 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.platform.db.migration.version.v84;
+
+import java.sql.SQLException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.db.CoreDbTester;
+
+import static java.sql.Types.BIGINT;
+import static java.sql.Types.VARCHAR;
+
+public class CreateSamlMessageIdsTableTest {
+
+ private static final String TABLE_NAME = "saml_message_ids";
+
+ @Rule
+ public CoreDbTester dbTester = CoreDbTester.createEmpty();
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private CreateSamlMessageIdsTable underTest = new CreateSamlMessageIdsTable(dbTester.database());
+
+ @Test
+ public void table_has_been_created() throws SQLException {
+ underTest.execute();
+
+ dbTester.assertTableExists(TABLE_NAME);
+ dbTester.assertPrimaryKey(TABLE_NAME, "pk_saml_message_ids", "uuid");
+ dbTester.assertUniqueIndex(TABLE_NAME, "saml_message_ids_unique", "message_id");
+
+ dbTester.assertColumnDefinition(TABLE_NAME, "uuid", VARCHAR, 40, false);
+ dbTester.assertColumnDefinition(TABLE_NAME, "message_id", VARCHAR, 255, false);
+ dbTester.assertColumnDefinition(TABLE_NAME, "expiration_date", BIGINT, 20, false);
+ dbTester.assertColumnDefinition(TABLE_NAME, "created_at", BIGINT, 20, false);
+ }
+
+}
import org.sonar.core.platform.Module;
import org.sonar.server.authentication.event.AuthenticationEventImpl;
-import org.sonar.server.authentication.purge.SessionTokensCleaner;
-import org.sonar.server.authentication.purge.SessionTokensCleanerExecutorServiceImpl;
+import org.sonar.server.authentication.purge.ExpiredSessionsCleaner;
+import org.sonar.server.authentication.purge.ExpiredSessionsCleanerExecutorServiceImpl;
public class AuthenticationModule extends Module {
@Override
OAuth2ContextFactory.class,
OAuthCsrfVerifier.class,
RequestAuthenticatorImpl.class,
- SessionTokensCleaner.class,
- SessionTokensCleanerExecutorServiceImpl.class,
+ ExpiredSessionsCleaner.class,
+ ExpiredSessionsCleanerExecutorServiceImpl.class,
UserLastConnectionDatesUpdaterImpl.class,
UserRegistrarImpl.class,
UserSessionInitializer.class);
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info 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.purge;
+
+import java.util.concurrent.TimeUnit;
+import org.sonar.api.Startable;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.server.util.GlobalLockManager;
+
+public class ExpiredSessionsCleaner implements Startable {
+
+ private static final Logger LOG = Loggers.get(ExpiredSessionsCleaner.class);
+
+ private static final long PERIOD_IN_SECONDS = 24 * 60 * 60L;
+ private static final String LOCK_NAME = "SessionCleaner";
+
+ private final ExpiredSessionsCleanerExecutorService executorService;
+ private final DbClient dbClient;
+ private final GlobalLockManager lockManager;
+
+ public ExpiredSessionsCleaner(ExpiredSessionsCleanerExecutorService executorService, DbClient dbClient, GlobalLockManager lockManager) {
+ this.executorService = executorService;
+ this.dbClient = dbClient;
+ this.lockManager = lockManager;
+ }
+
+ @Override
+ public void start() {
+ this.executorService.scheduleAtFixedRate(this::executePurge, 0, PERIOD_IN_SECONDS, TimeUnit.SECONDS);
+ }
+
+ private void executePurge() {
+ if (!lockManager.tryLock(LOCK_NAME)) {
+ return;
+ }
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ cleanExpiredSessionTokens(dbSession);
+ cleanExpiredSamlMessageIds(dbSession);
+ }
+ }
+
+ private void cleanExpiredSessionTokens(DbSession dbSession) {
+ LOG.debug("Start of cleaning expired session tokens");
+ int deletedSessionTokens = dbClient.sessionTokensDao().deleteExpired(dbSession);
+ dbSession.commit();
+ LOG.info("Purge of expired session tokens has removed {} elements", deletedSessionTokens);
+ }
+
+ private void cleanExpiredSamlMessageIds(DbSession dbSession) {
+ LOG.debug("Start of cleaning expired SAML message IDs");
+ int deleted = dbClient.samlMessageIdDao().deleteExpired(dbSession);
+ dbSession.commit();
+ LOG.info("Purge of expired SAML message ids has removed {} elements", deleted);
+ }
+
+ @Override
+ public void stop() {
+ // nothing to do
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info 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.purge;
+
+import java.util.concurrent.ScheduledExecutorService;
+import org.sonar.api.server.ServerSide;
+
+@ServerSide
+public interface ExpiredSessionsCleanerExecutorService extends ScheduledExecutorService {
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info 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.purge;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import org.sonar.server.util.AbstractStoppableScheduledExecutorServiceImpl;
+
+import static java.lang.Thread.MIN_PRIORITY;
+
+public class ExpiredSessionsCleanerExecutorServiceImpl
+ extends AbstractStoppableScheduledExecutorServiceImpl<ScheduledExecutorService>
+ implements ExpiredSessionsCleanerExecutorService {
+
+ public ExpiredSessionsCleanerExecutorServiceImpl() {
+ super(
+ Executors.newSingleThreadScheduledExecutor(r -> {
+ Thread thread = Executors.defaultThreadFactory().newThread(r);
+ thread.setName("ExpiredSessionsCleaner-%d");
+ thread.setPriority(MIN_PRIORITY);
+ thread.setDaemon(false);
+ return thread;
+ }));
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2020 SonarSource SA
- * mailto:info 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.purge;
-
-import java.util.concurrent.TimeUnit;
-import org.sonar.api.Startable;
-import org.sonar.api.config.Configuration;
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.server.util.GlobalLockManager;
-
-public class SessionTokensCleaner implements Startable {
-
- private static final Logger LOG = Loggers.get(SessionTokensCleaner.class);
-
- private static final String PURGE_DELAY_CONFIGURATION = "sonar.authentication.session.tokens.purge.delay";
- private static final long DEFAULT_PURGE_DELAY_IN_SECONDS = 24 * 60 * 60L;
- private static final String LOCK_NAME = "SessionCleaner";
-
- private final SessionTokensCleanerExecutorService executorService;
- private final DbClient dbClient;
- private final Configuration configuration;
- private final GlobalLockManager lockManager;
-
- public SessionTokensCleaner(SessionTokensCleanerExecutorService executorService, DbClient dbClient, Configuration configuration, GlobalLockManager lockManager) {
- this.executorService = executorService;
- this.dbClient = dbClient;
- this.configuration = configuration;
- this.lockManager = lockManager;
- }
-
- @Override
- public void start() {
- this.executorService.scheduleAtFixedRate(this::executePurge, 0, configuration.getLong(PURGE_DELAY_CONFIGURATION).orElse(DEFAULT_PURGE_DELAY_IN_SECONDS), TimeUnit.SECONDS);
- }
-
- private void executePurge() {
- if (!lockManager.tryLock(LOCK_NAME)) {
- return;
- }
- LOG.debug("Start of cleaning expired session tokens");
- try (DbSession dbSession = dbClient.openSession(false)) {
- int deletedSessionTokens = dbClient.sessionTokensDao().deleteExpired(dbSession);
- dbSession.commit();
- LOG.info("Purge of expired session tokens has removed {} elements", deletedSessionTokens);
- }
- }
-
- @Override
- public void stop() {
- // nothing to do
- }
-
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2020 SonarSource SA
- * mailto:info 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.purge;
-
-import java.util.concurrent.ScheduledExecutorService;
-import org.sonar.api.server.ServerSide;
-
-@ServerSide
-public interface SessionTokensCleanerExecutorService extends ScheduledExecutorService {
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2020 SonarSource SA
- * mailto:info 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.purge;
-
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import org.sonar.server.util.AbstractStoppableScheduledExecutorServiceImpl;
-
-import static java.lang.Thread.MIN_PRIORITY;
-
-public class SessionTokensCleanerExecutorServiceImpl
- extends AbstractStoppableScheduledExecutorServiceImpl<ScheduledExecutorService>
- implements SessionTokensCleanerExecutorService {
-
- public SessionTokensCleanerExecutorServiceImpl() {
- super(
- Executors.newSingleThreadScheduledExecutor(r -> {
- Thread thread = Executors.defaultThreadFactory().newThread(r);
- thread.setName("SessionTokensCleaner-%d");
- thread.setPriority(MIN_PRIORITY);
- thread.setDaemon(false);
- return thread;
- }));
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info 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.purge;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.impl.utils.TestSystem2;
+import org.sonar.api.utils.log.LogAndArguments;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.db.DbTester;
+import org.sonar.db.user.SamlMessageIdDto;
+import org.sonar.db.user.SessionTokenDto;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.util.AbstractStoppableExecutorService;
+import org.sonar.server.util.GlobalLockManager;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ExpiredSessionsCleanerTest {
+
+ private static final long NOW = 1_000_000_000L;
+
+ private TestSystem2 system2 = new TestSystem2().setNow(NOW);
+ @Rule
+ public DbTester db = DbTester.create(system2);
+ @Rule
+ public LogTester logTester = new LogTester();
+
+ private GlobalLockManager lockManager = mock(GlobalLockManager.class);
+
+ private SyncSessionTokensCleanerExecutorService executorService = new SyncSessionTokensCleanerExecutorService();
+
+ private ExpiredSessionsCleaner underTest = new ExpiredSessionsCleaner(executorService, db.getDbClient(), lockManager);
+
+ @Test
+ public void purge_expired_session_tokens() {
+ when(lockManager.tryLock(anyString())).thenReturn(true);
+ UserDto user = db.users().insertUser();
+ SessionTokenDto validSessionToken = db.users().insertSessionToken(user, st -> st.setExpirationDate(NOW + 1_000_000L));
+ SessionTokenDto expiredSessionToken = db.users().insertSessionToken(user, st -> st.setExpirationDate(NOW - 1_000_000L));
+ underTest.start();
+
+ executorService.runCommand();
+
+ assertThat(db.getDbClient().sessionTokensDao().selectByUuid(db.getSession(), validSessionToken.getUuid())).isPresent();
+ assertThat(db.getDbClient().sessionTokensDao().selectByUuid(db.getSession(), expiredSessionToken.getUuid())).isNotPresent();
+ assertThat(logTester.getLogs(LoggerLevel.INFO))
+ .extracting(LogAndArguments::getFormattedMsg)
+ .contains("Purge of expired session tokens has removed 1 elements");
+ }
+
+ @Test
+ public void purge_expired_saml_message_ids() {
+ when(lockManager.tryLock(anyString())).thenReturn(true);
+ db.getDbClient().samlMessageIdDao().insert(db.getSession(), new SamlMessageIdDto().setMessageId("MESSAGE_1").setExpirationDate(NOW + 1_000_000L));
+ db.getDbClient().samlMessageIdDao().insert(db.getSession(), new SamlMessageIdDto().setMessageId("MESSAGE_2").setExpirationDate(NOW - 1_000_000L));
+ db.commit();
+ underTest.start();
+
+ executorService.runCommand();
+
+ assertThat(db.getDbClient().samlMessageIdDao().selectByMessageId(db.getSession(), "MESSAGE_1")).isPresent();
+ assertThat(db.getDbClient().samlMessageIdDao().selectByMessageId(db.getSession(), "MESSAGE_2")).isNotPresent();
+ assertThat(logTester.getLogs(LoggerLevel.INFO))
+ .extracting(LogAndArguments::getFormattedMsg)
+ .contains("Purge of expired SAML message ids has removed 1 elements");
+ }
+
+ @Test
+ public void do_not_execute_purge_when_fail_to_get_lock() {
+ when(lockManager.tryLock(anyString())).thenReturn(false);
+ SessionTokenDto expiredSessionToken = db.users().insertSessionToken(db.users().insertUser(), st -> st.setExpirationDate(NOW - 1_000_000L));
+ underTest.start();
+
+ executorService.runCommand();
+
+ assertThat(db.getDbClient().sessionTokensDao().selectByUuid(db.getSession(), expiredSessionToken.getUuid())).isPresent();
+ }
+
+ private static class SyncSessionTokensCleanerExecutorService extends AbstractStoppableExecutorService<ScheduledExecutorService> implements ExpiredSessionsCleanerExecutorService {
+
+ private Runnable command;
+
+ public SyncSessionTokensCleanerExecutorService() {
+ super(null);
+ }
+
+ public void runCommand() {
+ command.run();
+ }
+
+ @Override
+ public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
+ this.command = command;
+ return null;
+ }
+
+ @Override
+ public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
+ return null;
+ }
+
+ @Override
+ public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
+ return null;
+ }
+
+ @Override
+ public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
+ return null;
+ }
+
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2020 SonarSource SA
- * mailto:info 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.purge;
-
-import java.util.concurrent.Callable;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.config.Configuration;
-import org.sonar.api.config.internal.MapSettings;
-import org.sonar.api.impl.utils.TestSystem2;
-import org.sonar.api.utils.log.LogAndArguments;
-import org.sonar.api.utils.log.LogTester;
-import org.sonar.api.utils.log.LoggerLevel;
-import org.sonar.db.DbTester;
-import org.sonar.db.user.SessionTokenDto;
-import org.sonar.db.user.UserDto;
-import org.sonar.server.util.AbstractStoppableExecutorService;
-import org.sonar.server.util.GlobalLockManager;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class SessionTokensCleanerTest {
-
- private static final long NOW = 1_000_000_000L;
-
- private TestSystem2 system2 = new TestSystem2().setNow(NOW);
- @Rule
- public DbTester db = DbTester.create(system2);
- @Rule
- public LogTester logTester = new LogTester();
-
- private GlobalLockManager lockManager = mock(GlobalLockManager.class);
-
- private final MapSettings settings = new MapSettings();
- private final Configuration configuration = settings.asConfig();
-
- private SyncSessionTokensCleanerExecutorService executorService = new SyncSessionTokensCleanerExecutorService();
-
- private SessionTokensCleaner underTest = new SessionTokensCleaner(executorService, db.getDbClient(), configuration, lockManager);
-
- @Test
- public void purge_expired_session_tokens() {
- when(lockManager.tryLock(anyString())).thenReturn(true);
- UserDto user = db.users().insertUser();
- SessionTokenDto validSessionToken = db.users().insertSessionToken(user);
- SessionTokenDto expiredSessionToken = db.users().insertSessionToken(user, st -> st.setExpirationDate(NOW - 1_000_000L));
- underTest.start();
-
- executorService.runCommand();
-
- assertThat(db.getDbClient().sessionTokensDao().selectByUuid(db.getSession(), validSessionToken.getUuid())).isPresent();
- assertThat(db.getDbClient().sessionTokensDao().selectByUuid(db.getSession(), expiredSessionToken.getUuid())).isNotPresent();
- assertThat(logTester.getLogs(LoggerLevel.INFO))
- .extracting(LogAndArguments::getFormattedMsg)
- .containsOnly("Purge of expired session tokens has removed 1 elements");
- }
-
- @Test
- public void do_not_execute_purge_when_fail_to_get_lock() {
- when(lockManager.tryLock(anyString())).thenReturn(false);
- SessionTokenDto expiredSessionToken = db.users().insertSessionToken(db.users().insertUser(), st -> st.setExpirationDate(NOW - 1_000_000L));
- underTest.start();
-
- executorService.runCommand();
-
- assertThat(db.getDbClient().sessionTokensDao().selectByUuid(db.getSession(), expiredSessionToken.getUuid())).isPresent();
- }
-
- private static class SyncSessionTokensCleanerExecutorService extends AbstractStoppableExecutorService<ScheduledExecutorService> implements SessionTokensCleanerExecutorService {
-
- private Runnable command;
-
- public SyncSessionTokensCleanerExecutorService() {
- super(null);
- }
-
- public void runCommand() {
- command.run();
- }
-
- @Override
- public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
- this.command = command;
- return null;
- }
-
- @Override
- public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
- return null;
- }
-
- @Override
- public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
- return null;
- }
-
- @Override
- public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
- return null;
- }
-
- }
-}