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

ID-690 Get User(s) Endpoint v2 #1208

Merged
merged 12 commits into from
Oct 19, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import org.broadinstitute.dsde.workbench.sam.db.TestDbReference
import org.broadinstitute.dsde.workbench.sam.db.tables._
import org.broadinstitute.dsde.workbench.sam.google.{GoogleExtensionRoutes, GoogleExtensions, GoogleGroupSynchronizer, GoogleKeyCache}
import org.broadinstitute.dsde.workbench.sam.model._
import org.broadinstitute.dsde.workbench.sam.model.api.SamUser
import org.broadinstitute.dsde.workbench.sam.service.UserService._
import org.broadinstitute.dsde.workbench.sam.service._
import org.broadinstitute.dsde.workbench.sam.util.SamRequestContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import org.broadinstitute.dsde.workbench.sam.azure.AzureService
import org.broadinstitute.dsde.workbench.sam.dataAccess.{AccessPolicyDAO, DirectoryDAO, StatefulMockAccessPolicyDaoBuilder}
import org.broadinstitute.dsde.workbench.sam.google.GoogleExtensions
import org.broadinstitute.dsde.workbench.sam.model._
import org.broadinstitute.dsde.workbench.sam.model.api.SamUser
import org.broadinstitute.dsde.workbench.sam.service._
import org.broadinstitute.dsde.workbench.sam.util.SamRequestContext
import org.broadinstitute.dsde.workbench.sam.{Generator, MockSamDependencies, MockTestSupport}
Expand Down
143 changes: 143 additions & 0 deletions src/main/resources/swagger/api-docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2793,6 +2793,102 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/ErrorReport'
/api/users/v2/self:
get:
tags:
- Users
summary: gets the user
operationId: getSamUserSelf
responses:
200:
description: user exists
content:
application/json:
schema:
$ref: '#/components/schemas/SamUserResponse'
404:
description: user not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorReport'
/api/users/v2/self/allowed:
get:
tags:
- Users
summary: gets the user allowances
operationId: getSamUserSelfAllowances
responses:
200:
description: user exists
content:
application/json:
schema:
$ref: '#/components/schemas/SamUserAllowances'
404:
description: user not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorReport'
/api/users/v2/{sam_user_id}:
get:
tags:
- Admin
summary: gets a user
description: Gets a SamUser by their id. This endpoint is scoped to the permissions of the caller.
A normal user can call the endpoint with their own id, but trying to get another user will result in a 404.
Admin permissions grant the caller the ability to get any id.
operationId: getSamUserById
parameters:
- name: sam_user_id
in: path
description: the id of the sam user to get
required: true
schema:
type: string
responses:
200:
description: user exists
content:
application/json:
schema:
$ref: '#/components/schemas/SamUserResponse'
404:
description: user not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorReport'
/api/users/v2/{sam_user_id}/allowed:
get:
tags:
- Admin
summary: gets a user
description: Gets a users allowances by their id. This endpoint is scoped to the permissions of the caller.
A normal user can call the endpoint with their own id, but trying to get another user will result in a 404.
Admin permissions grant the caller the ability to get any id.
operationId: getSamUserAllowancesById
parameters:
- name: sam_user_id
in: path
description: the id of the sam user to get
required: true
schema:
type: string
responses:
200:
description: user exists
content:
application/json:
schema:
$ref: '#/components/schemas/SamUserAllowances'
404:
description: user not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorReport'
/register/user/v1:
get:
tags:
Expand Down Expand Up @@ -3799,6 +3895,53 @@ components:
format: date-time
description: User's time of last update
description: specification of a User
SamUserResponse:
type: object
required:
- id
- email
- allowed
- createdAt
- updatedAt
properties:
id:
type: string
description: User's Id
googleSubjectId:
type: string
description: User's Google subject Id
email:
type: string
description: User's email address
format: email
azureB2CId:
type: string
description: User's Azure B2C Id
allowed:
type: boolean
description: Whether or not the user allowed to use the system
createdAt:
type: string
format: date-time
description: User's time of creation
registeredAt:
type: string
format: date-time
description: User's time of registration
updatedAt:
type: string
format: date-time
description: User's time of last update
description: specification of a User
SamUserAllowances:
type: object
properties:
allowed:
type: boolean
description: is the user allowed to use the system
details:
type: object
description: details of the user's allowances
UpdateUserRequest:
type: object
properties:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import akka.http.scaladsl.server.Directives._
import org.broadinstitute.dsde.workbench.model._
import org.broadinstitute.dsde.workbench.model.google.GoogleProject
import org.broadinstitute.dsde.workbench.sam.config.LiquibaseConfig
import org.broadinstitute.dsde.workbench.sam.model.SamJsonSupport._
import org.broadinstitute.dsde.workbench.sam.model.api.SamJsonSupport._
import org.broadinstitute.dsde.workbench.sam.model.SamResourceActions.{adminAddMember, adminReadPolicies, adminRemoveMember}
import org.broadinstitute.dsde.workbench.sam.model.SamResourceTypes.resourceTypeAdminName
import org.broadinstitute.dsde.workbench.sam.model._
import org.broadinstitute.dsde.workbench.sam.model.api.{AccessPolicyMembershipRequest, AdminUpdateUserRequest}
import org.broadinstitute.dsde.workbench.sam.model.api.{AccessPolicyMembershipRequest, AdminUpdateUserRequest, SamUser}
import org.broadinstitute.dsde.workbench.sam.service.ResourceService
import org.broadinstitute.dsde.workbench.sam.util.SamRequestContext
import spray.json.DefaultJsonProtocol._
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.broadinstitute.dsde.workbench.sam.api

import akka.http.scaladsl.server
import org.broadinstitute.dsde.workbench.sam.model.SamUser
import org.broadinstitute.dsde.workbench.sam.model.api.SamUser
import org.broadinstitute.dsde.workbench.sam.util.SamRequestContext

trait ExtensionRoutes {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import org.broadinstitute.dsde.workbench.model.WorkbenchIdentityJsonSupport._
import org.broadinstitute.dsde.workbench.model._
import org.broadinstitute.dsde.workbench.sam._
import org.broadinstitute.dsde.workbench.sam.model.{ResourceId, _}
import org.broadinstitute.dsde.workbench.sam.model.SamJsonSupport._
import org.broadinstitute.dsde.workbench.sam.model.api.SamJsonSupport._
import org.broadinstitute.dsde.workbench.sam.model.api.SamUser
import org.broadinstitute.dsde.workbench.sam.service.ManagedGroupService
import org.broadinstitute.dsde.workbench.sam.service.ManagedGroupService.ManagedGroupPolicyName
import org.broadinstitute.dsde.workbench.sam.util.SamRequestContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ import akka.http.scaladsl.server
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.{Directive0, ExceptionHandler}
import org.broadinstitute.dsde.workbench.model._
import org.broadinstitute.dsde.workbench.sam.model.SamJsonSupport._
import org.broadinstitute.dsde.workbench.sam.model.SamUser
import org.broadinstitute.dsde.workbench.sam.model.api.SamJsonSupport._
import org.broadinstitute.dsde.workbench.sam.service.UserService
import org.broadinstitute.dsde.workbench.sam.util.SamRequestContext
import spray.json.JsBoolean
Expand All @@ -17,7 +16,7 @@ import scala.concurrent.ExecutionContext

/** Created by mbemis on 5/22/17.
*/
trait UserRoutes extends SamUserDirectives with SamRequestContextDirectives {
trait OldUserRoutes extends SamUserDirectives with SamRequestContextDirectives {
implicit val executionContext: ExecutionContext
val userService: UserService

Expand All @@ -33,7 +32,7 @@ trait UserRoutes extends SamUserDirectives with SamRequestContextDirectives {
})
}

def userRoutes(samRequestContext: SamRequestContext): server.Route =
def oldUserRoutes(samRequestContext: SamRequestContext): server.Route =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should mark this as deprecated

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should only mark as deprecated once all the functionality is implemented in the V2 routes

pathPrefix("user") {
(pathPrefix("v1") | pathEndOrSingleSlash) {
pathEndOrSingleSlash {
Expand Down Expand Up @@ -151,33 +150,4 @@ trait UserRoutes extends SamUserDirectives with SamRequestContextDirectives {
}
}
}

def apiUserRoutes(samUser: SamUser, samRequestContext: SamRequestContext): server.Route = pathPrefix("users") {
Ghost-in-a-Jar marked this conversation as resolved.
Show resolved Hide resolved
pathPrefix("v1") {
get {
path(Segment) { email =>
pathEnd {
complete {
userService.getUserIdInfoFromEmail(WorkbenchEmail(email), samRequestContext).map {
case Left(_) => StatusCodes.NotFound -> None
case Right(None) => StatusCodes.NoContent -> None
case Right(Some(userIdInfo)) => StatusCodes.OK -> Some(userIdInfo)
}
}
}
}
} ~
pathPrefix("invite") {
post {
path(Segment) { inviteeEmail =>
complete {
userService
.inviteUser(WorkbenchEmail(inviteeEmail.trim), samRequestContext)
.map(userStatus => StatusCodes.Created -> userStatus)
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import org.broadinstitute.dsde.workbench.model.WorkbenchIdentityJsonSupport._
import org.broadinstitute.dsde.workbench.model._
import org.broadinstitute.dsde.workbench.sam.config.LiquibaseConfig
import org.broadinstitute.dsde.workbench.sam.model.RootPrimitiveJsonSupport._
import org.broadinstitute.dsde.workbench.sam.model.SamJsonSupport._
import org.broadinstitute.dsde.workbench.sam.model.api.SamJsonSupport._
import org.broadinstitute.dsde.workbench.sam.model._
import org.broadinstitute.dsde.workbench.sam.model.api.AccessPolicyMembershipRequest
import org.broadinstitute.dsde.workbench.sam.model.api.{AccessPolicyMembershipRequest, SamUser}
import org.broadinstitute.dsde.workbench.sam.service.ResourceService
import org.broadinstitute.dsde.workbench.sam.util.SamRequestContext
import spray.json.DefaultJsonProtocol._
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,16 @@ abstract class SamRoutes(
val openTelemetry: OpenTelemetryMetrics[IO]
) extends LazyLogging
with ResourceRoutes
with UserRoutes
with OldUserRoutes
with StatusRoutes
with TermsOfServiceRoutes
with ExtensionRoutes
with ManagedGroupRoutes
with AdminRoutes
with AzureRoutes
with ServiceAdminRoutes {
with ServiceAdminRoutes
with UserRoutesV1
with UserRoutesV2 {

def route: server.Route = (logRequestResult & handleExceptions(myExceptionHandler)) {
oidcConfig.swaggerRoutes("swagger/api-docs.yaml") ~
Expand All @@ -64,19 +66,20 @@ abstract class SamRoutes(
termsOfServiceRoutes ~
withExecutionContext(ExecutionContext.global) {
withSamRequestContext { samRequestContext =>
pathPrefix("register")(userRoutes(samRequestContext)) ~
pathPrefix("register")(oldUserRoutes(samRequestContext)) ~
pathPrefix("api") {
// these routes are for machine to machine authorized requests
// the whitelisted service admin account email is in the header of the request
serviceAdminRoutes(samRequestContext) ~
userRoutesV2(samRequestContext) ~
withActiveUser(samRequestContext) { samUser =>
val samRequestContextWithUser = samRequestContext.copy(samUser = Option(samUser))
resourceRoutes(samUser, samRequestContextWithUser) ~
adminRoutes(samUser, samRequestContextWithUser) ~
extensionRoutes(samUser, samRequestContextWithUser) ~
groupRoutes(samUser, samRequestContextWithUser) ~
apiUserRoutes(samUser, samRequestContextWithUser) ~
azureRoutes(samUser, samRequestContextWithUser)
azureRoutes(samUser, samRequestContextWithUser) ~
userRoutesV1(samUser, samRequestContextWithUser)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import org.broadinstitute.dsde.workbench.sam._
import org.broadinstitute.dsde.workbench.sam.api.RejectionHandlers.termsOfServiceRejectionHandler
import org.broadinstitute.dsde.workbench.sam.config.AppConfig.AdminConfig
import org.broadinstitute.dsde.workbench.sam.config.TermsOfServiceConfig
import org.broadinstitute.dsde.workbench.sam.model.SamJsonSupport._
import org.broadinstitute.dsde.workbench.sam.model.{SamUser, TermsOfServiceAcceptance}
import org.broadinstitute.dsde.workbench.sam.model.api.SamJsonSupport._
import org.broadinstitute.dsde.workbench.sam.model.TermsOfServiceAcceptance
import org.broadinstitute.dsde.workbench.sam.model.api.SamUser
import org.broadinstitute.dsde.workbench.sam.util.SamRequestContext

/** Directives to get user information.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import akka.http.scaladsl.model.StatusCodes.{NotFound, OK}
import akka.http.scaladsl.server
import akka.http.scaladsl.server.Directives._
import org.broadinstitute.dsde.workbench.model._
import org.broadinstitute.dsde.workbench.sam.model.SamJsonSupport._
import org.broadinstitute.dsde.workbench.sam.model.api.SamJsonSupport._
import org.broadinstitute.dsde.workbench.sam.service.ResourceService
import org.broadinstitute.dsde.workbench.sam.util.SamRequestContext
import spray.json.DefaultJsonProtocol._
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import org.broadinstitute.dsde.workbench.model._
import org.broadinstitute.dsde.workbench.model.google.ServiceAccountSubjectId
import org.broadinstitute.dsde.workbench.sam.api.StandardSamUserDirectives._
import org.broadinstitute.dsde.workbench.sam.azure.ManagedIdentityObjectId
import org.broadinstitute.dsde.workbench.sam.model.SamUser
import org.broadinstitute.dsde.workbench.sam.model.api.SamUser
import org.broadinstitute.dsde.workbench.sam.service.UserService._
import org.broadinstitute.dsde.workbench.sam.service.{TosService, UserService}
import org.broadinstitute.dsde.workbench.sam.service.UserService
import org.broadinstitute.dsde.workbench.sam.util.SamRequestContext

import scala.concurrent.ExecutionContext
Expand All @@ -27,7 +27,7 @@ trait StandardSamUserDirectives extends SamUserDirectives with LazyLogging with

def withActiveUser(samRequestContext: SamRequestContext): Directive1[SamUser] = requireOidcHeaders.flatMap { oidcHeaders =>
onSuccess {
getActiveSamUser(oidcHeaders, userService, tosService, samRequestContext).unsafeToFuture()
getActiveSamUser(oidcHeaders, userService, samRequestContext).unsafeToFuture()
}.tmap { samUser =>
logger.debug(s"Handling request for active Sam User: $samUser")
samUser
Expand Down Expand Up @@ -119,15 +119,15 @@ object StandardSamUserDirectives {
loadUserMaybeUpdateAzureB2CId(azureB2CId, oidcHeaders.googleSubjectIdFromAzure, userService, samRequestContext)
}

def getActiveSamUser(oidcHeaders: OIDCHeaders, userService: UserService, tosService: TosService, samRequestContext: SamRequestContext): IO[SamUser] =
def getActiveSamUser(oidcHeaders: OIDCHeaders, userService: UserService, samRequestContext: SamRequestContext): IO[SamUser] =
for {
user <- getSamUser(oidcHeaders, userService, samRequestContext)
tosComplianceDetails <- tosService.getTosComplianceStatus(user, samRequestContext)
allowances <- userService.getUserAllowances(user, samRequestContext)
} yield {
if (!tosComplianceDetails.permitsSystemUsage) {
if (!allowances.getTermsOfServiceCompliance) {
throw new WorkbenchExceptionWithErrorReport(ErrorReport(StatusCodes.Unauthorized, "User must accept the latest terms of service."))
}
if (!user.enabled) {
if (!allowances.getEnabled) {
throw new WorkbenchExceptionWithErrorReport(ErrorReport(StatusCodes.Unauthorized, "User is disabled."))
}

Expand Down
Loading
Loading