Skip to content

Commit

Permalink
More updates to resolve superuser promotion feature
Browse files Browse the repository at this point in the history
  • Loading branch information
ljdelight committed Nov 22, 2023
1 parent 3857f87 commit c563684
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 17 deletions.
28 changes: 27 additions & 1 deletion app/org/maproulette/framework/controller/UserController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -557,27 +557,53 @@ class UserController @Inject() (
}
}

/**
* Get the current list of superusers
* @return the list of maproulette user ids that are superusers
*/
def getSuperUserIds: Action[AnyContent] = Action.async { implicit request =>
implicit val requireSuperUser: Boolean = true
this.sessionManager.authenticatedRequest { implicit user =>
Ok(Json.toJson(this.serviceManager.user.superUsers))
}
}

/**
* Promotes a user to a super user
* @param maprouletteUserId the maproulette user id to promote
* @return NoCnotent if successful or if the user is already a super user
*/
def promoteUserToSuperUser(maprouletteUserId: Long): Action[AnyContent] = Action.async {
implicit request =>
implicit val requireSuperUser: Boolean = true
this.sessionManager.authenticatedRequest { implicit grantorUser =>
this.serviceManager.user.retrieve(maprouletteUserId) match {
case Some(user) =>
this.serviceManager.user.promoteUserToSuperUser(user, grantorUser)
this.serviceManager.user.clearCache(user.id)
NoContent
case None =>
throw new NotFoundException(s"Could not find user with ID $maprouletteUserId")
}
}
}

/**
* Demotes a user from a super user to a normal user
* @param maprouletteUserId the maproulette user id to demote
* @return NoContent if successful or if the user is already a normal user
*/
def demoteSuperUserToUser(maprouletteUserId: Long): Action[AnyContent] = Action.async {
implicit request =>
implicit val requireSuperUser: Boolean = true
this.sessionManager.authenticatedRequest { implicit grantorUser =>
this.serviceManager.user.retrieve(maprouletteUserId) match {
case Some(user) =>
this.serviceManager.user.promoteUserToSuperUser(user, grantorUser)
if (user.id == grantorUser.id) {
throw new IllegalAccessException("A superuser cannot demote themselves")
}
this.serviceManager.user.demoteSuperUserToUser(user)
this.serviceManager.user.clearCache(user.id)
NoContent
case None =>
throw new NotFoundException(s"Could not find user with ID $maprouletteUserId")
Expand Down
10 changes: 0 additions & 10 deletions app/org/maproulette/framework/repository/UserRepository.scala
Original file line number Diff line number Diff line change
Expand Up @@ -237,16 +237,6 @@ class UserRepository @Inject() (
}
}

def getSuperUserIdsFromDatabase(): List[Long] = {
db.withConnection { implicit c =>
// Search the grants table for grantee_id (eg the user's maproulette id) where the role is -1 (superuser)
// and the grantee_type is 5 (user).
anorm
.SQL("SELECT grantee_id AS id FROM grants WHERE role = -1 AND grantee_type = 5")
.as(SqlParser.scalar[Long].*)
}
}

def getUserTaskCounts(userId: Long, dateFilter: DateParameter)(
implicit c: Option[Connection] = None
): Map[String, Int] = {
Expand Down
33 changes: 30 additions & 3 deletions app/org/maproulette/framework/service/GrantService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@

package org.maproulette.framework.service

import anorm.SqlParser

import javax.inject.{Inject, Singleton}
import org.maproulette.Config
import org.maproulette.exception.InvalidException
import org.maproulette.framework.model.{Grant, Grantee, GrantTarget, User}
import org.maproulette.framework.model.{Grant, GrantTarget, Grantee, User}
import org.maproulette.framework.psql.Query
import org.maproulette.framework.psql.filter.{Parameter, BaseParameter, Operator}
import org.maproulette.framework.repository.{GrantRepository}
import org.maproulette.framework.psql.filter.{BaseParameter, Operator, Parameter}
import org.maproulette.framework.repository.GrantRepository
import org.maproulette.data._
import org.maproulette.permissions.Permission

Expand Down Expand Up @@ -196,6 +198,31 @@ class GrantService @Inject() (
)
}

def getSuperUserIdsFromDatabase: List[Long] = {
repository.withMRConnection { implicit c =>
// Search the grants table for grantee_id (eg the user's maproulette id) where the role is -1 (superuser)
// and the grantee_type is 5 (user).
anorm
.SQL("SELECT grantee_id AS id FROM grants WHERE role = -1 AND grantee_type = 5")
.as(SqlParser.scalar[Long].*)
}
}

def deleteSuperUserFromDatabase(maprouletteUserId: Long): Boolean = {
if (maprouletteUserId == User.DEFAULT_SUPER_USER_ID) {
throw new InvalidException("The system super user is not allowed to be removed")
}
repository.withMRTransaction { implicit c =>
// Delete the grantee_id (eg the user's maproulette id) where the role is -1 (superuser).
anorm
.SQL(
"DELETE FROM grants WHERE grantee_id = {maprouletteUserId} AND role = -1 AND grantee_type = 5"
)
.on("maprouletteUserId" -> maprouletteUserId)
.executeUpdate() >= 1
}
}

/**
* Generates List of query Parameters setup to match the given filter
* arguments
Expand Down
22 changes: 21 additions & 1 deletion app/org/maproulette/framework/service/UserService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,26 @@ class UserService @Inject() (
}
}

def demoteSuperUserToUser(user: User): Boolean = {
if (!isSuperUser(user.id)) {
logger.info(
s"MapRoulette uid=${user.id} (osm_id=${user.osmProfile.id}) is not a superuser, skipping role demotion"
)
return true
}

logger.warn(s"Removing superuser role for uid=${user.id} (osm_id=${user.osmProfile.id})")
if (serviceManager.grant.deleteSuperUserFromDatabase(user.id)) {
superUsers -= user.id
true
} else {
logger.warn(
s"Failed to remove superuser role for uid=${user.id} (osm_id=${user.osmProfile.id})"
)
false
}
}

def promoteSuperUsersInConfig(): Unit = {
val usersToPromote: List[User] = config.superAccounts
.filter(_ != "*")
Expand Down Expand Up @@ -118,7 +138,7 @@ class UserService @Inject() (
}

private def seedSuperUserIds(): Unit = {
superUsers ++= repository.getSuperUserIdsFromDatabase()
superUsers ++= grantService.getSuperUserIdsFromDatabase
}

/**
Expand Down
24 changes: 22 additions & 2 deletions conf/v2_route/user.api
Original file line number Diff line number Diff line change
Expand Up @@ -598,13 +598,31 @@ DELETE /user/:userId/project/:projectId/:role @org.maproulette.framework.contr
DELETE /user/project/:projectId/:role @org.maproulette.framework.controller.UserController.removeUsersFromProject(projectId:Long, role:Int, isOSMUserId:Boolean ?= false)
###
# tags: [ User ]
# summary: Get all current superusers
# description: Return a list of maproulette user ids who are superusers. The requesting user must be a super user.
# responses:
# '200':
# description: The list was obtained and the response contains the list of superusers
# content:
# application/json:
# schema:
# type: array
# items:
# type: integer
# format: int64
# '401':
# description: The user is not authorized to make this request
###
GET /user/superusers @org.maproulette.framework.controller.UserController.getSuperUserIds()
###
# tags: [ User ]
# summary: Promote a standard user to a super user
# description: Promote a standard user, a 'grantee', to a super user role; the requesting user is called a 'grantor'.
# This will add the superuser role to the user, allowing the grantee to perform super user actions.
# The grantor must be a super user.
# responses:
# '204':
# description: The user was promoted to a superuser (or was already a superuser)
# description: The user was promoted to a superuser or was already a superuser
# '401':
# description: The grantor is not authorized to make this request
# '404':
Expand All @@ -623,9 +641,11 @@ PUT /user/superuser/:userId @org.maproulette.framework.controller.UserController
# The grantor must be a superuser.
# responses:
# '204':
# description: The superuser role was removed from the user
# description: The superuser role was removed from the user or the user was not a superuser
# '401':
# description: The grantor is not authorized to make this request
# '403':
# description: Use 403 Forbidden if the grantor is trying to demote themselves
# '404':
# description: The grantee was not found
# parameters:
Expand Down

0 comments on commit c563684

Please sign in to comment.