Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pivotal ID # 187921156: Enable users with ADMIN rights for a collection to set back release dates for public submissions #857

Merged
merged 4 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ import java.time.temporal.ChronoUnit.DAYS

fun Instant.asOffsetAtStartOfDay(): OffsetDateTime = atOffset(UTC).toLocalDate().atStartOfDay().atOffset(UTC)

fun OffsetDateTime.atStartOfDay(): OffsetDateTime = toInstant().asOffsetAtStartOfDay()

fun Instant.asOffsetAtEndOfDay(): OffsetDateTime = plus(1, DAYS).atOffset(UTC).toLocalDate().atStartOfDay().minusSeconds(1).atOffset(UTC)
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ sealed class InvalidPermissionsException(message: String) : RuntimeException(mes

class UserCanNotProvideAccessNumber(
user: String,
) : InvalidPermissionsException("The user {$user} is not allowed to provide accession number directly")
) : InvalidPermissionsException("The user $user is not allowed to provide accession number directly")

class UserCanNotUpdateSubmit(
accNo: String,
user: String,
) : InvalidPermissionsException("The user {$user} is not allowed to update the submission $accNo")
) : InvalidPermissionsException("The user $user is not allowed to update the submission $accNo")

class UserCanNotSubmitCollectionsException(
user: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ package ac.uk.ebi.biostd.submission.exceptions

class PastReleaseDateException : RuntimeException("Release date cannot be in the past")

class InvalidReleaseDateException : RuntimeException("The release date of a public study cannot be changed")
class InvalidReleaseException : RuntimeException("The release date of a public study cannot be changed")

class UnreleasedSubmissionException : RuntimeException("Can't generate FTP links for a private submission")
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package ac.uk.ebi.biostd.submission.service

import ac.uk.ebi.biostd.submission.exceptions.InvalidDateFormatException
import ac.uk.ebi.biostd.submission.exceptions.InvalidReleaseDateException
import ac.uk.ebi.biostd.submission.exceptions.InvalidReleaseException
import ac.uk.ebi.biostd.submission.exceptions.PastReleaseDateException
import ac.uk.ebi.biostd.submission.model.SubmitRequest
import ebi.ac.uk.base.orFalse
import ebi.ac.uk.model.extensions.releaseDate
import ebi.ac.uk.security.integration.components.IUserPrivilegesService
import ebi.ac.uk.util.date.asOffsetAtStartOfDay
import ebi.ac.uk.util.date.atStartOfDay
import ebi.ac.uk.util.date.isBeforeOrEqual
import java.time.Instant
import java.time.LocalDate
Expand All @@ -19,46 +19,30 @@ import java.time.ZoneOffset.UTC
* Calculates the submission release date based on current state of the submission in the system. Calculation rules.
*/
class TimesService(
private val privilegesService: IUserPrivilegesService,
private val privileges: IUserPrivilegesService,
) {
internal fun getTimes(request: SubmitRequest): Times {
internal fun getTimes(rqt: SubmitRequest): Times {
val now = OffsetDateTime.now()
val creationTime = request.previousVersion?.creationTime ?: now
val releaseTime = request.submission.releaseDate?.let { releaseTime(now, it, request) }
val creationTime = rqt.previousVersion?.creationTime ?: now
val releaseTime = rqt.submission.releaseDate?.let { parseDate(it) }
val released = releaseTime?.isBeforeOrEqual(now).orFalse()
val submitter = rqt.submitter.email

if (releaseTime != null && privileges.canUpdateReleaseDate(submitter).not()) checkReleaseTime(rqt, releaseTime)
return Times(creationTime, now, releaseTime, released)
}

private fun releaseTime(
now: OffsetDateTime,
releaseDate: String,
request: SubmitRequest,
): OffsetDateTime {
val releaseTime = parseDate(releaseDate)
val user = request.submitter.email
val today = now.toInstant().asOffsetAtStartOfDay()
val isReleased = request.previousVersion?.released.orFalse()
val previousReleaseTime = request.previousVersion?.releaseTime?.toInstant()?.asOffsetAtStartOfDay()

fun checkFutureReleaseTime() =
when {
isReleased && privilegesService.canSuppress(user).not() -> throw InvalidReleaseDateException()
else -> releaseTime
}

fun checkPastReleaseTime() =
when {
isReleased -> throw InvalidReleaseDateException()
previousReleaseTime == null -> throw PastReleaseDateException()
else -> releaseTime
}

return when {
previousReleaseTime?.isEqual(releaseTime).orFalse() -> releaseTime
releaseTime.isAfter(today) -> checkFutureReleaseTime()
releaseTime.isBefore(today) -> checkPastReleaseTime()
else -> releaseTime
@Suppress("ThrowsCount")
private fun checkReleaseTime(
rqt: SubmitRequest,
releaseTime: OffsetDateTime,
) {
val today = OffsetDateTime.now().atStartOfDay()

when (val previous = rqt.previousVersion) {
null -> if (releaseTime < today) throw PastReleaseDateException()
else ->
if (previous.released && releaseTime != previous.releaseTime) throw InvalidReleaseException()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class AccNoServiceTest(

val error = assertThrows<UserCanNotProvideAccessNumber> { testInstance.calculateAccNo(request) }
assertThat(error.message)
.isEqualTo("The user {submiter@email.com} is not allowed to provide accession number directly")
.isEqualTo("The user submiter@email.com is not allowed to provide accession number directly")
}

@Test
Expand Down Expand Up @@ -155,7 +155,7 @@ class AccNoServiceTest(

val error = assertThrows<UserCanNotUpdateSubmit> { testInstance.calculateAccNo(request) }
assertThat(error.message)
.isEqualTo("The user {$SUBMITTER} is not allowed to update the submission $ACC_NO")
.isEqualTo("The user $SUBMITTER is not allowed to update the submission $ACC_NO")
}

@Test
Expand Down Expand Up @@ -188,7 +188,7 @@ class AccNoServiceTest(
val error = assertThrows<UserCanNotUpdateSubmit> { testInstance.calculateAccNo(request) }

assertThat(error.message).isEqualTo(
"The user {submiter@email.com} is not allowed to update the submission AAB12",
"The user submiter@email.com is not allowed to update the submission AAB12",
)
}
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,9 @@ interface IUserPrivilegesService {

fun canRelease(email: String): Boolean

fun canSuppress(email: String): Boolean
/**
* Permission to indicate the given user is able to release in the past, make a submission private or change a
* public study release date.
*/
fun canUpdateReleaseDate(email: String): Boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ internal class UserPrivilegesService(
private val userRepository: UserDataRepository,
private val tagsDataRepository: AccessTagDataRepo,
private val submissionQueryService: SubmissionMetaQueryService,
private val userPermissionsService: UserPermissionsService,
private val permissionsService: UserPermissionsService,
) : IUserPrivilegesService {
override fun canProvideAccNo(email: String) = isSuperUser(email)

Expand All @@ -41,7 +41,7 @@ internal class UserPrivilegesService(
when {
isSuperUser(email) -> tagsDataRepository.findAll().map { it.name }
else ->
userPermissionsService
permissionsService
.allowedTags(email, accessType)
.map { it.accessTag.name }
.distinct()
Expand All @@ -67,16 +67,16 @@ internal class UserPrivilegesService(

override fun canRelease(email: String): Boolean = isSuperUser(email)

override fun canSuppress(email: String): Boolean = isSuperUser(email)
override fun canUpdateReleaseDate(email: String): Boolean = isSuperUser(email)

private suspend fun hasPermissions(
user: String,
accNo: String,
accessType: AccessType,
): Boolean {
val collections = submissionQueryService.getCollections(accNo)
return userPermissionsService.hasPermission(user, accNo, accessType) ||
(collections.isNotEmpty() && collections.all { userPermissionsService.hasPermission(user, it, accessType) })
return permissionsService.hasPermission(user, accNo, accessType) ||
(collections.isNotEmpty() && collections.all { permissionsService.hasPermission(user, it, accessType) })
}

private fun isSuperUser(email: String) = getUser(email).superuser
Expand All @@ -86,7 +86,7 @@ internal class UserPrivilegesService(
accNo: String,
): Boolean {
val collections = submissionQueryService.getCollections(accNo)
return collections.isNotEmpty() && collections.all { userPermissionsService.isAdmin(email, it) }
return collections.isNotEmpty() && collections.all { permissionsService.isAdmin(email, it) }
}

private fun isAuthor(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,13 @@ class UserPrivilegesServiceTest(

@Test
fun `super user suppress`() {
assertThat(testInstance.canSuppress("superuser@mail.com")).isTrue()
assertThat(testInstance.canUpdateReleaseDate("superuser@mail.com")).isTrue()
}

@Test
fun `regular user suppress`() {
every { superuser.superuser } returns false
assertThat(testInstance.canSuppress("superuser@mail.com")).isFalse()
assertThat(testInstance.canUpdateReleaseDate("superuser@mail.com")).isFalse()
}

private fun initUsers() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ class SecurityUtilTest(
}

@Test
fun `check password is set as invalid when normal user security token is used`() {
fun `check password is set as invalid when Regular user security token is used`() {
val userToken = testInstance.createToken(simpleUser)
every { userRepository.getReferenceById(SecurityTestEntities.USER_ID) } returns simpleUser

Expand Down
Loading