Skip to content

Commit

Permalink
Merge pull request #590 from NDLANO/arena-enabled
Browse files Browse the repository at this point in the history
Arena enabled
  • Loading branch information
gunnarvelle authored Jan 30, 2025
2 parents 1be9bf6 + efe5eeb commit 8c8021d
Show file tree
Hide file tree
Showing 9 changed files with 63 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ import sttp.tapir.*
import sttp.tapir.server.PartialServerEndpoint

trait MyNDLAAuthHelpers {
this: UserService with TapirErrorHandling =>
this: UserService & TapirErrorHandling =>

object MyNDLAAuthHelpers {
implicit class authlessEndpointFeideExtension[A, I, E, O, R](self: Endpoint[Unit, I, AllErrors, O, R]) {
type MaybeFeideToken = Option[FeideAccessToken]
type PartialFeideEndpoint[F[_]] = PartialServerEndpoint[MaybeFeideToken, MyNDLAUser, I, AllErrors, O, R, F]
private type MaybeFeideToken = Option[FeideAccessToken]
private type PartialFeideEndpoint[F[_]] =
PartialServerEndpoint[MaybeFeideToken, MyNDLAUser, I, AllErrors, O, R, F]
def requireMyNDLAUser[F[_]](
requireArena: Boolean = false,
requireArenaAdmin: Boolean = false
Expand All @@ -37,7 +38,7 @@ trait MyNDLAAuthHelpers {
case Left(err) => Left(err)
}
} else if (requireArena) userService.getArenaEnabledUser(maybeToken).handleErrorsOrOk
else userService.getMyNdlaUserDataDomain(maybeToken, List.empty).handleErrorsOrOk
else userService.getMyNdlaUserDataDomain(maybeToken).handleErrorsOrOk
}
val securityLogic = (m: MonadError[F]) => (a: Option[FeideAccessToken]) => m.unit(authFunc(a))
PartialServerEndpoint(newEndpoint, securityLogic)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,7 @@ trait ArenaReadService {
for {
feideToken <- feideApiClient.getFeideAccessTokenOrFail(feideAccessToken)
feideId <- feideApiClient.getFeideID(feideAccessToken)
user <- userService.getOrCreateMyNDLAUserIfNotExist(feideId, feideAccessToken, List.empty)(session)
user <- userService.getOrCreateMyNDLAUserIfNotExist(feideId, feideAccessToken)(session)
nodebbUserId <- nodebb.getUserId(feideToken)
_ <- arenaRepository.disconnectPostsByUser(user.id)(session)
_ <- arenaRepository.disconnectTopicsByUser(user.id)(session)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,10 +180,8 @@ trait FolderConverterService {
}

def toApiUserData(
domainUserData: DomainMyNDLAUser,
arenaEnabledOrgs: List[String]
domainUserData: DomainMyNDLAUser
): model.api.myndla.MyNDLAUserDTO = {
val arenaEnabled = getArenaEnabled(domainUserData, arenaEnabledOrgs)
model.api.myndla.MyNDLAUserDTO(
id = domainUserData.id,
feideId = domainUserData.feideId,
Expand All @@ -194,7 +192,7 @@ trait FolderConverterService {
role = domainUserData.userRole.toString,
organization = domainUserData.organization,
groups = domainUserData.groups.map(toApiGroup),
arenaEnabled = arenaEnabled,
arenaEnabled = domainUserData.arenaEnabled,
arenaAccepted = domainUserData.arenaAccepted,
arenaGroups = domainUserData.arenaGroups
)
Expand Down Expand Up @@ -275,20 +273,14 @@ trait FolderConverterService {
updatedUser: UpdatedMyNDLAUserDTO,
updaterToken: Option[TokenUser],
updaterUser: Option[DomainMyNDLAUser],
arenaEnabledUsers: List[String],
arenaEnabledOrgs: List[String],
feideToken: Option[FeideAccessToken]
): Try[DomainMyNDLAUser] = {
val favoriteSubjects = updatedUser.favoriteSubjects.getOrElse(domainUserData.favoriteSubjects)
val arenaEnabled = {
if (updaterToken.hasPermission(LEARNINGPATH_API_ADMIN) || updaterUser.exists(_.isAdmin))
updatedUser.arenaEnabled.getOrElse(domainUserData.arenaEnabled)
else
domainUserData.arenaEnabled || arenaEnabledUsers
.map(_.toLowerCase)
.contains(domainUserData.email.toLowerCase) || arenaEnabledOrgs
.map(_.toLowerCase)
.contains(domainUserData.organization.toLowerCase)
domainUserData.arenaEnabled
}

val arenaAccepted = getArenaAccepted(arenaEnabled, domainUserData, updatedUser, feideToken).?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,12 +271,10 @@ trait FolderReadService {
feideAccessToken: Option[FeideAccessToken]
): Try[MyNDLAUserDTO] =
for {
users <- configService.getMyNDLAEnabledUsers
user <- userRepository.rollbackOnFailure(session =>
userService.getOrCreateMyNDLAUserIfNotExist(feideId, feideAccessToken, users)(session)
userService.getOrCreateMyNDLAUserIfNotExist(feideId, feideAccessToken)(session)
)
orgs <- configService.getMyNDLAEnabledOrgs
} yield folderConverterService.toApiUserData(user, orgs)
} yield folderConverterService.toApiUserData(user)

def getStats: Option[api.StatsDTO] = {
implicit val session: DBSession = folderRepository.getSession(true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ trait FolderWriteService {

private def getMyNDLAUser(feideId: FeideID, feideAccessToken: Option[FeideAccessToken]): Try[MyNDLAUser] = {
userRepository.rollbackOnFailure(session =>
userService.getOrCreateMyNDLAUserIfNotExist(feideId, feideAccessToken, List.empty)(session)
userService.getOrCreateMyNDLAUserIfNotExist(feideId, feideAccessToken)(session)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,49 +57,43 @@ trait UserService {
}

def getMyNdlaUserDataDomain(
feideAccessToken: Option[FeideAccessToken],
arenaEnabledUsers: List[String]
feideAccessToken: Option[FeideAccessToken]
): Try[MyNDLAUser] = {
for {
feideId <- feideApiClient.getFeideID(feideAccessToken)
userData <- userRepository.rollbackOnFailure(session =>
getOrCreateMyNDLAUserIfNotExist(feideId, feideAccessToken, arenaEnabledUsers)(session)
getOrCreateMyNDLAUserIfNotExist(feideId, feideAccessToken)(session)
)
} yield userData
}

def getArenaEnabledUser(feideAccessToken: Option[FeideAccessToken]): Try[MyNDLAUser] = {
for {
users <- configService.getMyNDLAEnabledUsers
userData <- getMyNdlaUserDataDomain(feideAccessToken, users)
orgs <- configService.getMyNDLAEnabledOrgs
arenaEnabled = folderConverterService.getArenaEnabled(userData, orgs)
user <- if (arenaEnabled) Success(userData) else Failure(AccessDeniedException("User is not arena enabled"))
userData <- getMyNdlaUserDataDomain(feideAccessToken)
user <-
if (userData.arenaEnabled) Success(userData) else Failure(AccessDeniedException("User is not arena enabled"))
} yield user
}

def getMyNDLAUserData(feideAccessToken: Option[FeideAccessToken]): Try[myndla.MyNDLAUserDTO] = {
for {
users <- configService.getMyNDLAEnabledUsers
userData <- getMyNdlaUserDataDomain(feideAccessToken, users)
orgs <- configService.getMyNDLAEnabledOrgs
api = folderConverterService.toApiUserData(userData, orgs)
userData <- getMyNdlaUserDataDomain(feideAccessToken)
api = folderConverterService.toApiUserData(userData)
} yield api
}

def getOrCreateMyNDLAUserIfNotExist(
feideId: FeideID,
feideAccessToken: Option[FeideAccessToken],
arenaEnabledUsers: List[String]
feideAccessToken: Option[FeideAccessToken]
)(implicit session: DBSession): Try[MyNDLAUser] = {
userRepository.reserveFeideIdIfNotExists(feideId)(session).flatMap {
case false => createMyNDLAUser(feideId, feideAccessToken, arenaEnabledUsers)(session)
case false => createMyNDLAUser(feideId, feideAccessToken)(session)
case true =>
userRepository.userWithFeideId(feideId)(session).flatMap {
case None => Failure(new IllegalStateException(s"User with feide_id $feideId was not found."))
case Some(userData) if userData.wasUpdatedLast24h => Success(userData)
case Some(userData) =>
fetchDataAndUpdateMyNDLAUser(feideId, feideAccessToken, userData, arenaEnabledUsers)(session)
fetchDataAndUpdateMyNDLAUser(feideId, feideAccessToken, userData)(session)
}
}
}
Expand All @@ -117,7 +111,7 @@ trait UserService {
implicit session: DBSession
): Try[myndla.MyNDLAUserDTO] =
for {
existingUser <- userService.getOrCreateMyNDLAUserIfNotExist(feideId, feideAccessToken, List.empty)(session)
existingUser <- userService.getOrCreateMyNDLAUserIfNotExist(feideId, feideAccessToken)(session)
newFavorites = (existingUser.favoriteSubjects ++ userData.favoriteSubjects).distinct
updatedFeideUser = UpdatedMyNDLAUserDTO(
favoriteSubjects = Some(newFavorites),
Expand All @@ -136,19 +130,15 @@ trait UserService {
for {
_ <- folderWriteService.canWriteDuringMyNDLAWriteRestrictionsOrAccessDenied(feideId, feideAccessToken)
existingUserData <- getMyNDLAUserOrFail(feideId)
enabledUsers <- configService.getMyNDLAEnabledUsers
enabledOrgs <- configService.getMyNDLAEnabledOrgs
combined <- folderConverterService.mergeUserData(
existingUserData,
updatedUser,
None,
Some(existingUserData),
enabledUsers,
enabledOrgs,
feideAccessToken
)
updated <- userRepository.updateUser(feideId, combined)
api = folderConverterService.toApiUserData(updated, enabledOrgs)
api = folderConverterService.toApiUserData(updated)
} yield api
}

Expand All @@ -159,22 +149,18 @@ trait UserService {
updaterMyNdla: Option[MyNDLAUser]
)(session: DBSession = AutoSession): Try[myndla.MyNDLAUserDTO] = {
for {
existing <- userService.getUserById(userId)(session)
enabledUsers <- configService.getMyNDLAEnabledUsers
enabledOrgs <- configService.getMyNDLAEnabledOrgs
existing <- userService.getUserById(userId)(session)
converted <- folderConverterService.mergeUserData(
existing,
updatedUser,
updaterToken,
updaterMyNdla,
enabledUsers,
enabledOrgs,
// NOTE: This token is used to create a nodebb profile
// since the one updating here is an admin, we cannot use it to create a profile.
feideToken = None
)
updated <- userRepository.updateUserById(userId, converted)(session)
api = folderConverterService.toApiUserData(updated, enabledOrgs)
api = folderConverterService.toApiUserData(updated)
} yield api
}

Expand Down Expand Up @@ -206,24 +192,24 @@ trait UserService {

private def createMyNDLAUser(
feideId: FeideID,
feideAccessToken: Option[FeideAccessToken],
arenaEnabledUsers: List[String]
feideAccessToken: Option[FeideAccessToken]
)(implicit
session: DBSession
): Try[MyNDLAUser] = {
for {
feideExtendedUserData <- feideApiClient.getFeideExtendedUser(feideAccessToken)
organization <- feideApiClient.getOrganization(feideAccessToken)
feideGroups <- feideApiClient.getFeideGroups(feideAccessToken)
userRole = if (feideExtendedUserData.isTeacher) UserRole.EMPLOYEE else UserRole.STUDENT
newUser = MyNDLAUserDocument(
favoriteSubjects = Seq.empty,
userRole = if (feideExtendedUserData.isTeacher) UserRole.EMPLOYEE else UserRole.STUDENT,
userRole = userRole,
lastUpdated = clock.now().plusDays(1),
organization = organization,
groups = toDomainGroups(feideGroups),
username = feideExtendedUserData.username,
email = feideExtendedUserData.email,
arenaEnabled = arenaEnabledUsers.map(_.toLowerCase).contains(feideExtendedUserData.email.toLowerCase),
arenaEnabled = userRole == UserRole.EMPLOYEE,
arenaGroups = getInitialIsArenaGroups(feideId),
displayName = feideExtendedUserData.displayName,
arenaAccepted = false
Expand All @@ -235,26 +221,25 @@ trait UserService {
private def fetchDataAndUpdateMyNDLAUser(
feideId: FeideID,
feideAccessToken: Option[FeideAccessToken],
userData: MyNDLAUser,
arenaEnabledUsers: List[String]
userData: MyNDLAUser
)(implicit
session: DBSession
): Try[MyNDLAUser] = {
val feideUser = feideApiClient.getFeideExtendedUser(feideAccessToken).?
val organization = feideApiClient.getOrganization(feideAccessToken).?
val feideGroups = feideApiClient.getFeideGroups(feideAccessToken).?
val userRole = if (feideUser.isTeacher) UserRole.EMPLOYEE else UserRole.STUDENT
val updatedMyNDLAUser = MyNDLAUser(
id = userData.id,
feideId = userData.feideId,
favoriteSubjects = userData.favoriteSubjects,
userRole = if (feideUser.isTeacher) UserRole.EMPLOYEE else UserRole.STUDENT,
userRole = userRole,
lastUpdated = clock.now().plusDays(1),
organization = organization,
groups = toDomainGroups(feideGroups),
username = feideUser.username,
email = feideUser.email,
arenaEnabled =
userData.arenaEnabled || arenaEnabledUsers.map(_.toLowerCase).contains(feideUser.email.toLowerCase),
arenaEnabled = userData.arenaEnabled || userRole == UserRole.EMPLOYEE,
displayName = feideUser.displayName,
arenaGroups = userData.arenaGroups,
arenaAccepted = userData.arenaAccepted
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ class FolderConverterServiceTest extends UnitTestSuite with TestEnvironment {
arenaGroups = List.empty
)

service.toApiUserData(domainUserData, List.empty) should be(expectedUserData)
service.toApiUserData(domainUserData) should be(expectedUserData)
}

test("That mergeUserData works correctly") {
Expand Down Expand Up @@ -563,13 +563,13 @@ class FolderConverterServiceTest extends UnitTestSuite with TestEnvironment {
arenaAccepted = true
)

service.mergeUserData(domainUserData, updatedUserData1, None, None, List.empty, List.empty, None).get should be(
service.mergeUserData(domainUserData, updatedUserData1, None, None, None).get should be(
expectedUserData1
)
service.mergeUserData(domainUserData, updatedUserData2, None, None, List.empty, List.empty, None).get should be(
service.mergeUserData(domainUserData, updatedUserData2, None, None, None).get should be(
expectedUserData2
)
service.mergeUserData(domainUserData, updatedUserData3, None, None, List.empty, List.empty, None).get should be(
service.mergeUserData(domainUserData, updatedUserData3, None, None, None).get should be(
expectedUserData3
)
}
Expand Down
Loading

0 comments on commit 8c8021d

Please sign in to comment.