Skip to content

Commit

Permalink
Merge pull request #209 from broadinstitute/inviteuser
Browse files Browse the repository at this point in the history
add invite endpoint
  • Loading branch information
Qi77Qi committed Sep 21, 2018
2 parents db57d56 + 40a3585 commit 5250340
Show file tree
Hide file tree
Showing 11 changed files with 256 additions and 127 deletions.
35 changes: 35 additions & 0 deletions src/main/resources/swagger/api-docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1091,6 +1091,28 @@ paths:
tags:
- Resources

/api/v1/invite/user/{inviteeEmail}:
post:
summary: create current user in the system using login credentials
parameters:
- in: path
description: email for the user you want to invite
name: inviteeEmail
required: true
type: string
responses:
201:
description: 'user successfully created'
schema:
$ref: '#/definitions/UserStatusDetails'
409:
description: user already exists
schema:
$ref: '#/definitions/ErrorReport'
operationId: inviteUser
tags:
- Users

/register/user/v1:
get:
summary: gets the registration status of the logged in user
Expand Down Expand Up @@ -1505,6 +1527,19 @@ definitions:
type: boolean
description: true if the user is enabled in ldap

UserStatusDetails:
description: ''
required:
- userSubjectId
- userEmail
properties:
userSubjectId:
type: string
description: user id
userEmail:
type: string
description: user email

UserStatusDiagnostics:
description: ''
required:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ abstract class SamRoutes(val resourceService: ResourceService, val userService:
swaggerRoutes ~
statusRoutes ~
pathPrefix("register") { userRoutes } ~
pathPrefix("api") { resourceRoutes ~ adminUserRoutes ~ extensionRoutes ~ groupRoutes }
pathPrefix("api") { inviteRoute ~ resourceRoutes ~ adminUserRoutes ~ extensionRoutes ~ groupRoutes }
}

// basis for logRequestResult lifted from http://stackoverflow.com/questions/32475471/how-does-one-log-akka-http-client-requests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ trait StandardUserInfoDirectives extends UserInfoDirectives {
) tflatMap {
case (token, googleSubjectId, expiresIn, email) =>
onSuccess(Try(expiresIn.toLong).fold[Future[UserInfo]](
t => Future.failed(new WorkbenchExceptionWithErrorReport(ErrorReport(StatusCodes.BadRequest, s"expiresIn $expiresIn can't be converted to Long because of $t"))),
l => getUserInfo(OAuth2BearerToken(token), GoogleSubjectId(googleSubjectId), WorkbenchEmail(email), l, directoryDAO)))
t =>
Future.failed(new WorkbenchExceptionWithErrorReport(ErrorReport(StatusCodes.BadRequest, s"expiresIn $expiresIn can't be converted to Long because of $t"))),
l =>
getUserInfo(OAuth2BearerToken(token), GoogleSubjectId(googleSubjectId), WorkbenchEmail(email), l, directoryDAO)))
}

def requireCreateUser: Directive1[CreateWorkbenchUser] = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import org.broadinstitute.dsde.workbench.model.google.GoogleProject
import org.broadinstitute.dsde.workbench.model._
import org.broadinstitute.dsde.workbench.sam.model.SamJsonSupport._
import org.broadinstitute.dsde.workbench.sam.service.UserService
import org.broadinstitute.dsde.workbench.sam.service.UserService.genWorkbenchUserId

import scala.concurrent.ExecutionContext

Expand All @@ -21,64 +22,68 @@ trait UserRoutes extends UserInfoDirectives {

def userRoutes: server.Route =
pathPrefix("user") {
(pathPrefix("v1") | pathEndOrSingleSlash) {
(pathPrefix("v1") | pathEndOrSingleSlash) {
pathEndOrSingleSlash {
post {
requireCreateUser { createUser =>
complete {
userService.createUser(createUser).map(userStatus => StatusCodes.Created -> userStatus)
}
}
} ~ requireUserInfo { user =>
get {
parameter("userDetailsOnly".?) { userDetailsOnly =>
complete {
userService.getUserStatus(user.userId, userDetailsOnly.exists(_.equalsIgnoreCase("true"))).map {
statusOption =>
statusOption
.map { status => StatusCodes.OK -> Option(status)
}
.getOrElse(StatusCodes.NotFound -> None)
}
}
}
}
}
}
} ~ pathPrefix("v2") {
pathPrefix("self") {
pathEndOrSingleSlash {
post {
requireCreateUser { createUser =>
complete {
userService.createUser(createUser).map(userStatus => StatusCodes.Created -> userStatus)
}
}
} ~ requireUserInfo { user =>
}
} ~ requireUserInfo { user =>
path("info") {
get {
parameter("userDetailsOnly".?) { userDetailsOnly =>
complete {
userService.getUserStatus(user.userId, userDetailsOnly.exists(_.equalsIgnoreCase("true"))).map { statusOption =>
statusOption.map { status =>
StatusCodes.OK -> Option(status)
}.getOrElse(StatusCodes.NotFound -> None)
}
complete {
userService.getUserStatusInfo(user.userId).map { statusOption =>
statusOption
.map { status => StatusCodes.OK -> Option(status)
}
.getOrElse(StatusCodes.NotFound -> None)
}
}
}
}
}
} ~ pathPrefix("v2") {
pathPrefix("self") {
pathEndOrSingleSlash {
post {
requireCreateUser { createUser =>
complete {
userService.createUser(createUser).map(userStatus => StatusCodes.Created -> userStatus)
}
}
}
}~ requireUserInfo {user =>
path("info") {
get {
complete {
userService.getUserStatusInfo(user.userId).map { statusOption =>
statusOption.map { status =>
StatusCodes.OK -> Option(status)
}.getOrElse(StatusCodes.NotFound -> None)
}
}
}
} ~
path("diagnostics") {
get {
complete {
userService.getUserStatusDiagnostics(user.userId).map { statusOption =>
statusOption.map { status =>
StatusCodes.OK -> Option(status)
}.getOrElse(StatusCodes.NotFound -> None)
}
} ~
path("diagnostics") {
get {
complete {
userService.getUserStatusDiagnostics(user.userId).map { statusOption =>
statusOption
.map { status => StatusCodes.OK -> Option(status)
}
}
.getOrElse(StatusCodes.NotFound -> None)
}
}
}
}
}
}
}
}
}

def adminUserRoutes: server.Route =
Expand All @@ -89,68 +94,93 @@ trait UserRoutes extends UserInfoDirectives {
path("email" / Segment) { email =>
complete {
userService.getUserStatusFromEmail(WorkbenchEmail(email)).map { statusOption =>
statusOption.map { status =>
StatusCodes.OK -> Option(status)
}.getOrElse(StatusCodes.NotFound -> None)
statusOption
.map { status => StatusCodes.OK -> Option(status)
}
.getOrElse(StatusCodes.NotFound -> None)
}
}
} ~
pathPrefix(Segment) { userId =>
pathEnd {
delete {
complete {
userService.deleteUser(WorkbenchUserId(userId), userInfo).map(_ => StatusCodes.OK)
}
} ~
get {
complete {
userService.getUserStatus(WorkbenchUserId(userId)).map { statusOption =>
statusOption.map { status =>
StatusCodes.OK -> Option(status)
}.getOrElse(StatusCodes.NotFound -> None)
}
}
}
} ~
pathPrefix("enable") {
pathEndOrSingleSlash {
put {
pathPrefix(Segment) { userId =>
pathEnd {
delete {
complete {
userService.enableUser(WorkbenchUserId(userId), userInfo).map { statusOption =>
statusOption.map { status =>
StatusCodes.OK -> Option(status)
}.getOrElse(StatusCodes.NotFound -> None)
userService.deleteUser(WorkbenchUserId(userId), userInfo).map(_ => StatusCodes.OK)
}
} ~
get {
complete {
userService.getUserStatus(WorkbenchUserId(userId)).map { statusOption =>
statusOption
.map { status => StatusCodes.OK -> Option(status)
}
.getOrElse(StatusCodes.NotFound -> None)
}
}
}
}
}
} ~
pathPrefix("disable") {
pathEndOrSingleSlash {
put {
complete {
userService.disableUser(WorkbenchUserId(userId), userInfo).map { statusOption =>
statusOption.map { status =>
StatusCodes.OK -> Option(status)
}.getOrElse(StatusCodes.NotFound -> None)
} ~
pathPrefix("enable") {
pathEndOrSingleSlash {
put {
complete {
userService.enableUser(WorkbenchUserId(userId), userInfo).map { statusOption =>
statusOption
.map { status => StatusCodes.OK -> Option(status)
}
.getOrElse(StatusCodes.NotFound -> None)
}
}
}
}
}
}
} ~
pathPrefix("petServiceAccount") {
path(Segment) { project =>
delete {
complete {
cloudExtensions.deleteUserPetServiceAccount(WorkbenchUserId(userId), GoogleProject(project)).map(_ => StatusCodes.NoContent)
} ~
pathPrefix("disable") {
pathEndOrSingleSlash {
put {
complete {
userService.disableUser(WorkbenchUserId(userId), userInfo).map { statusOption =>
statusOption
.map { status => StatusCodes.OK -> Option(status)
}
.getOrElse(StatusCodes.NotFound -> None)
}
}
}
}
} ~
pathPrefix("petServiceAccount") {
path(Segment) { project =>
delete {
complete {
cloudExtensions
.deleteUserPetServiceAccount(WorkbenchUserId(userId), GoogleProject(project))
.map(_ => StatusCodes.NoContent)
}
}
}
}
}
}
}
}
}
}

val inviteRoute: server.Route = pathPrefix("v1"){
pathPrefix("invite") {
pathPrefix("user") {
post {
requireUserInfo { userInfo =>
path(Remaining) { email =>
complete {
userService
.inviteUser(InviteUser(genWorkbenchUserId(System.currentTimeMillis()), WorkbenchEmail(email)))
.map(userStatus => StatusCodes.Created -> userStatus)
}
}
}
}
}
}
}
}

final case class InviteUser(inviteeId: WorkbenchUserId, inviteeEmail: WorkbenchEmail)
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,33 @@ import javax.naming.NameNotFoundException
import com.typesafe.scalalogging.LazyLogging
import org.apache.commons.codec.binary.Hex
import org.broadinstitute.dsde.workbench.model._
import org.broadinstitute.dsde.workbench.sam.api.CreateWorkbenchUser
import org.broadinstitute.dsde.workbench.sam.api.{CreateWorkbenchUser, InviteUser}
import org.broadinstitute.dsde.workbench.sam.directory.DirectoryDAO
import org.broadinstitute.dsde.workbench.sam.model._

import scala.concurrent.{ExecutionContext, Future}

/**
* Created by dvoet on 7/14/17.
*/
class UserService(val directoryDAO: DirectoryDAO, val cloudExtensions: CloudExtensions)(implicit val executionContext: ExecutionContext) extends LazyLogging {

def createUser(user: CreateWorkbenchUser): Future[UserStatus] = {
for {
def createUser(user: CreateWorkbenchUser): Future[UserStatus] = for {
allUsersGroup <- cloudExtensions.getOrCreateAllUsersGroup(directoryDAO)
createdUser <- registerUser(user)
_ <- directoryDAO.enableIdentity(createdUser.id)
_ <- directoryDAO.addGroupMember(allUsersGroup.id, createdUser.id)
_ <- cloudExtensions.onUserCreate(createdUser)
userStatus <- getUserStatus(createdUser.id)
} yield {
userStatus.getOrElse(throw new WorkbenchException("getUserStatus returned None after user was created"))
}
}
res <- userStatus.toRight(new WorkbenchException("getUserStatus returned None after user was created")).fold(Future.failed, Future.successful)
} yield res

def inviteUser(invitee: InviteUser): Future[UserStatusDetails] = for {
existingSubject <- directoryDAO.loadSubjectFromEmail(invitee.inviteeEmail)
createdUser <- existingSubject match{
case None => directoryDAO.createUser(WorkbenchUser(invitee.inviteeId, None, invitee.inviteeEmail))
case Some(__) => Future.failed(new WorkbenchExceptionWithErrorReport(ErrorReport(StatusCodes.Conflict, s"email ${invitee.inviteeEmail} already exists")))
}
} yield UserStatusDetails(createdUser.id, createdUser.email)

/**
* If googleSubjectId exists in ldap, return 409; else if email also exists, we lookup pre-created user record and update
Expand Down
Loading

0 comments on commit 5250340

Please sign in to comment.