Class DropboxMockServiceHandler

java.lang.Object
edu.internet2.middleware.grouper.j2ee.MockServiceHandler
edu.internet2.middleware.grouper.app.dropbox.DropboxMockServiceHandler

public class DropboxMockServiceHandler extends MockServiceHandler
Mock implementation of the Dropbox Business Team API, used by the Dropbox provisioner tests.

This handler mirrors TrueFoundryMockServiceHandler: it stands in for the real Dropbox service behind the /mockServices/dropbox/... servlet path, backs the API onto three mock tables (mock_dropbox_group, mock_dropbox_user, mock_dropbox_membership) via Hibernate/GcDbAccess, and returns JSON shaped exactly as DropboxApiCommands sends and expects.

Protocol notes. Every Dropbox Team API call is an HTTP POST with a JSON object body and a JSON response; unions carry a ".tag" discriminator. Unlike the real service (which can defer long-running writes behind an async_job_id and a .../job_status/... poll), this mock always completes synchronously: deletes/removes return {".tag":"complete"} and members/add_v2 returns {".tag":"complete","complete":[...]} with no async_job_id. That keeps the tests off the polling path in DropboxApiCommands.pollIfAsync(java.util.Map<java.lang.String, java.lang.Object>, java.lang.String, com.fasterxml.jackson.databind.JsonNode, java.lang.String).

Auth difference from TrueFoundry. TrueFoundry stores a JSON blob of two tokens in accessTokenPassword and accepts either; Dropbox uses a single plain bearer token, so checkAuthorization(MockServiceRequest) compares the Authorization: Bearer value to the stored accessTokenPassword string directly, with no JSON parse.

Admin roles. Dropbox exposes no "list all roles" endpoint, so this mock publishes a fixed catalog of the 8 built-in admin roles, each mapped to a synthetic, deterministic role_id of "pid_dbtmr:" + lowercase(name) (see roleIdForName(String) / roleNameForRoleId(String)). A member's current role is stored as a single name in mock_dropbox_user.admin_role (null == member_only). The roles[] array emitted by member reads is harvested by DropboxApiCommands to learn the name -> role_id mapping it needs for set_admin_permissions_v2.

  • Field Details

    • doNotLogHeaders

      public static final Set<String> doNotLogHeaders
      the bearer token header carries the secret, so never log it
    • MOCK_OAUTH_ACCESS_TOKEN

      public static final String MOCK_OAUTH_ACCESS_TOKEN
      the access token the mock /oauth2/token endpoint issues (and checkAuthorization accepts)
      See Also:
  • Constructor Details

    • DropboxMockServiceHandler

      public DropboxMockServiceHandler()
  • Method Details

    • doNotLogHeaders

      public Set<String> doNotLogHeaders()
      Overrides:
      doNotLogHeaders in class MockServiceHandler
    • doNotLogParameters

      public Set<String> doNotLogParameters()
      Overrides:
      doNotLogParameters in class MockServiceHandler
    • ensureDropboxMockTables

      public static void ensureDropboxMockTables()
      Lazily create the three Dropbox mock tables (group, user, membership) if any is missing. Mirrors TrueFoundryMockServiceHandler.ensureTrueFoundryMockTables.
    • checkAuthorization

      public void checkAuthorization(MockServiceRequest mockServiceRequest)
      Validate the Authorization: Bearer <token> header against the configured Dropbox bearer token. Dropbox stores a single plain token in accessTokenPassword (no JSON blob like TrueFoundry), so the comparison is a direct string equals.
      Parameters:
      mockServiceRequest - the incoming request
    • oauth2Token

      public void oauth2Token(MockServiceRequest mockServiceRequest, MockServiceResponse mockServiceResponse)
      POST /oauth2/token (refresh-token grant). Simulates the Dropbox OAuth2 token endpoint: ignores the form body (the real endpoint validates grant_type/refresh_token/client_id/client_secret) and returns a fixed short-lived access token with a 4h expiry. No bearer auth -- the real token endpoint authenticates via the client credentials in the body.
    • groupsList

      public void groupsList(MockServiceRequest mockServiceRequest, MockServiceResponse mockServiceResponse)
      POST /2/team/groups/list (and /list/continue). Return every group as a GroupSummary. Paging is simulated as a single page: has_more is always false, so the /continue endpoint (which shares this method) simply returns the same full set with no further pages.
    • groupsGetInfo

      public void groupsGetInfo(MockServiceRequest mockServiceRequest, MockServiceResponse mockServiceResponse)
      POST /2/team/groups/get_info. Body is a GroupsSelector union {".tag":"group_ids","group_ids":[..]}. Returns an ARRAY of GroupsGetInfoItem: each known id yields {".tag":"group_info", ...GroupFullInfo with members[]}, each unknown id yields {".tag":"id_not_found","id_not_found":id}.
    • groupsCreate

      public void groupsCreate(MockServiceRequest mockServiceRequest, MockServiceResponse mockServiceResponse)
      POST /2/team/groups/create. Body = DropboxGroup.toCreateJson() (group_name, optional group_external_id, group_management_type union). Assign a native group_id of "g:" + uuid, persist, and return the new group's GroupFullInfo.
    • groupsUpdate

      public void groupsUpdate(MockServiceRequest mockServiceRequest, MockServiceResponse mockServiceResponse)
      POST /2/team/groups/update. Body carries a GroupSelector by group_id plus the new_* fields. Apply new_group_name / new_group_external_id and return the updated GroupFullInfo.
    • groupsDelete

      public void groupsDelete(MockServiceRequest mockServiceRequest, MockServiceResponse mockServiceResponse)
      POST /2/team/groups/delete. Body {".tag":"group_id","group_id":".."}. Delete the group and all of its memberships, then return a SYNCHRONOUS LaunchEmptyResult {".tag":"complete"} (the mock never defers behind an async_job_id).
    • groupsMembersAdd

      public void groupsMembersAdd(MockServiceRequest mockServiceRequest, MockServiceResponse mockServiceResponse)
      POST /2/team/groups/members/add. Body carries a GroupSelector plus a members[] array of {"user":UserSelectorArg,"access_type":{".tag":..}}. Insert a membership row per entry (idempotent on group_id + team_member_id) and return a GroupMembersChangeResult {"group_info":GroupFullInfo} with no async_job_id.
    • groupsMembersRemove

      public void groupsMembersRemove(MockServiceRequest mockServiceRequest, MockServiceResponse mockServiceResponse)
      POST /2/team/groups/members/remove. Body carries a GroupSelector plus a users[] array of UserSelectorArg. Delete the matching membership rows and return a GroupMembersChangeResult.
    • groupsMembersList

      public void groupsMembersList(MockServiceRequest mockServiceRequest, MockServiceResponse mockServiceResponse)
      POST /2/team/groups/members/list (and /list/continue). Return the group's members as GroupMemberInfo objects. Single-page mock: has_more is always false.
    • membersListV2

      public void membersListV2(MockServiceRequest mockServiceRequest, MockServiceResponse mockServiceResponse)
      POST /2/team/members/list_v2 (and /list/continue_v2). Return every member as {"profile":TeamMemberProfile,"roles":[TeamMemberRole]}. Single-page mock: has_more false.
    • membersGetInfoV2

      public void membersGetInfoV2(MockServiceRequest mockServiceRequest, MockServiceResponse mockServiceResponse)
      POST /2/team/members/get_info_v2. Body {"members":[UserSelectorArg]} where each selector tag is external_id | email | team_member_id. Return {"members_info":[{".tag":"member_info","profile":..,"roles":..}]}; an unknown selector yields {".tag":"id_not_found"}.
    • membersAddV2

      public void membersAddV2(MockServiceRequest mockServiceRequest, MockServiceResponse mockServiceResponse)
      POST /2/team/members/add_v2. Body {"new_members":[..],"force_async":false}; each entry carries member_email / member_given_name / member_surname / member_external_id. Assign a native team_member_id of "dbmid:" + uuid and an account_id, set status active, persist, and return a SYNCHRONOUS {".tag":"complete","complete":[{".tag":"success","success":{"profile":..,"role":..}}]}.
    • membersSetProfileV2

      public void membersSetProfileV2(MockServiceRequest mockServiceRequest, MockServiceResponse mockServiceResponse)
      POST /2/team/members/set_profile_v2. Body carries a UserSelectorArg plus new_email / new_external_id / new_given_name / new_surname. Apply by team_member_id and return the updated TeamMemberInfoV2 {"profile":TeamMemberProfile}.
    • membersRemove

      public void membersRemove(MockServiceRequest mockServiceRequest, MockServiceResponse mockServiceResponse)
      POST /2/team/members/remove. Body {"user":UserSelectorArg,"wipe_data":..,"keep_account":..}. Hard-delete the member (and all of their memberships) and return a SYNCHRONOUS LaunchEmptyResult {".tag":"complete"}.
    • membersRecover

      public void membersRecover(MockServiceRequest mockServiceRequest, MockServiceResponse mockServiceResponse)
      POST /2/team/members/recover. Body {"user":{".tag":"email","email":..}}. Restore a removed-but-recoverable member; the mock has no separate removed state, so it simply finds the member by selector and confirms it active, returning an empty object. A member not found returns the not-found error (the real API returns 409 user_unrecoverable / user_not_found).
    • membersSuspend

      public void membersSuspend(MockServiceRequest mockServiceRequest, MockServiceResponse mockServiceResponse)
      POST /2/team/members/suspend. Body {"user":UserSelectorArg,"wipe_data":..}. Set the member's status to suspended and return an empty object {} (the real API replies 200 with no body).
    • membersUnsuspend

      public void membersUnsuspend(MockServiceRequest mockServiceRequest, MockServiceResponse mockServiceResponse)
      POST /2/team/members/unsuspend. Body {"user":UserSelectorArg}. Set the member's status back to active and return an empty object {}.
    • membersSetAdminPermissionsV2

      public void membersSetAdminPermissionsV2(MockServiceRequest mockServiceRequest, MockServiceResponse mockServiceResponse)
      POST /2/team/members/set_admin_permissions_v2. Body {"user":UserSelectorArg,"new_roles":[role_id,..]}. Map the (first) role_id back to its admin-role NAME via the fixed catalog and store it in admin_role; an empty new_roles list clears the role (member_only == null). Return {"team_member_id":..}.
    • membersListMemberRoles

      public void membersListMemberRoles(MockServiceRequest mockServiceRequest, MockServiceResponse mockServiceResponse)
      POST /2/team/members/list_member_roles. Body {"team_member_id":".."}. Returns the full set of ASSIGNABLE admin roles for the team as {"roles":[TeamMemberRole]} -- i.e. the complete 8-role catalog, NOT just the member's current role. This mirrors the real Dropbox endpoint (which lists roles a member can be assigned) and is what lets a caller resolve a role NAME to a role_id on a fresh team where no member yet holds a role.
    • roleIdForName

      public static String roleIdForName(String roleName)
      Map an admin-role NAME to its fixed synthetic role_id ("pid_dbtmr:" + lowercase(name)).
      Parameters:
      roleName - the admin-role name
      Returns:
      the synthetic role_id
    • roleNameForRoleId

      public static String roleNameForRoleId(String roleId)
      Reverse-map a synthetic role_id back to its admin-role NAME by scanning the fixed 8-role catalog. Returns null when the id matches no known role.
      Parameters:
      roleId - the synthetic role_id
      Returns:
      the admin-role name, or null
    • handleRequest

      public void handleRequest(MockServiceRequest mockServiceRequest, MockServiceResponse mockServiceResponse)
      Route the incoming mock request. All Dropbox Team API calls are POSTs whose path after /mockServices/dropbox is a fixed endpoint (e.g. /2/team/groups/list). The path segments are joined and dispatched to the matching handler method, mirroring the TrueFoundry mock's POST-plus-path routing style.
      Specified by:
      handleRequest in class MockServiceHandler
      Parameters:
      mockServiceRequest - the request
      mockServiceResponse - the response