diff --git a/tests/TestHelpers/GraphHelper.php b/tests/TestHelpers/GraphHelper.php index b9112b7868..3c9dcab235 100644 --- a/tests/TestHelpers/GraphHelper.php +++ b/tests/TestHelpers/GraphHelper.php @@ -1673,6 +1673,48 @@ class GraphHelper { } } + /** + * @param array $shareeIds + * @param array $shareTypes + * @param string|null $permissionsRole + * @param string|null $permissionsAction + * @param string|null $expireDate + * + * @return array + * @throws \Exception + */ + public static function createShareInviteBody( + array $shareeIds, + array $shareTypes, + ?string $permissionsRole, + ?string $permissionsAction, + ?string $expireDate + ): array { + $body = []; + + foreach ($shareeIds as $index => $shareeId) { + $shareType = $shareTypes[$index]; + $body['recipients'][] = [ + "@libre.graph.recipient.type" => $shareType, + "objectId" => $shareeId + ]; + } + + if ($permissionsRole !== null) { + $roleId = self::getPermissionsRoleIdByName($permissionsRole); + $body['roles'] = [$roleId]; + } + + if ($permissionsAction !== null) { + $body['@libre.graph.permissions.actions'] = ['libre.graph/driveItem/' . $permissionsAction]; + } + + if ($expireDate !== null) { + $body['expirationDateTime'] = $expireDate; + } + return $body; + } + /** * @param string $baseUrl * @param string $xRequestId @@ -1704,28 +1746,7 @@ class GraphHelper { ?string $expireDate ): ResponseInterface { $url = self::getBetaFullUrl($baseUrl, "drives/$spaceId/items/$itemId/invite"); - $body = []; - - foreach ($shareeIds as $index => $shareeId) { - $shareType = $shareTypes[$index]; - $body['recipients'][] = [ - "@libre.graph.recipient.type" => $shareType, - "objectId" => $shareeId - ]; - } - - if ($permissionsRole !== null) { - $roleId = self::getPermissionsRoleIdByName($permissionsRole); - $body['roles'] = [$roleId]; - } - - if ($permissionsAction !== null) { - $body['@libre.graph.permissions.actions'] = ['libre.graph/driveItem/' . $permissionsAction]; - } - - if ($expireDate !== null) { - $body['expirationDateTime'] = $expireDate; - } + $body = self::createShareInviteBody($shareeIds, $shareTypes, $permissionsRole, $permissionsAction, $expireDate); return HttpRequestHelper::post( $url, @@ -2040,4 +2061,44 @@ class GraphHelper { self::getRequestHeaders() ); } + + /** + * @param string $baseUrl + * @param string $xRequestId + * @param string $user + * @param string $password + * @param string $spaceId + * @param array $shareeIds + * @param array $shareTypes + * @param string|null $permissionsRole + * @param string|null $permissionsAction + * @param string|null $expireDate + * + * @return ResponseInterface + * @throws \Exception|GuzzleException + */ + public static function sendSharingInvitationForDrive( + string $baseUrl, + string $xRequestId, + string $user, + string $password, + string $spaceId, + array $shareeIds, + array $shareTypes, + ?string $permissionsRole, + ?string $permissionsAction, + ?string $expireDate + ): ResponseInterface { + $url = self::getBetaFullUrl($baseUrl, "drives/$spaceId/root/invite"); + $body = self::createShareInviteBody($shareeIds, $shareTypes, $permissionsRole, $permissionsAction, $expireDate); + + return HttpRequestHelper::post( + $url, + $xRequestId, + $user, + $password, + self::getRequestHeaders(), + \json_encode($body) + ); + } } diff --git a/tests/acceptance/features/apiSharingNg/shareInvitations.feature b/tests/acceptance/features/apiSharingNg/shareInvitations.feature index 148863a32a..42b77a59ba 100644 --- a/tests/acceptance/features/apiSharingNg/shareInvitations.feature +++ b/tests/acceptance/features/apiSharingNg/shareInvitations.feature @@ -2356,13 +2356,13 @@ Feature: Send a sharing invitations """ Examples: | permissions-role | error-message | - | Space Viewer | role not applicable to this resource | + | Space Viewer | role not applicable to this resource | | Space Editor | role not applicable to this resource | | Manager | role not applicable to this resource | | Co Owner | Key: 'DriveItemInvite.Roles' Error:Field validation for 'Roles' failed on the 'available_role' tag | - Scenario Outline: try to send share invitation with re-sharing permissions + Scenario Outline: try to send share invitation with different re-sharing permissions Given group "grp1" has been created And user "Alice" has created folder "FolderToShare" And the following users have been added to the following groups @@ -2389,3 +2389,829 @@ Feature: Send a sharing invitations | permissions/update | | permissions/delete | | permissions/deny | + + + Scenario Outline: invite user to a project space with different roles using root endpoint + Given using spaces DAV path + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And user "Alice" has created a space "NewSpace" with the default quota using the Graph API + When user "Alice" sends the following share invitation using root endpoint of the Graph API: + | space | NewSpace | + | sharee | Brian | + | shareType | user | + | permissionsRole | | + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": { + "type": "object", + "required": [ + "grantedToV2", + "roles" + ], + "properties": { + "grantedToV2": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "user": { + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "const": "Brian Murphy" + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "roles": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": { + "type": "string", + "pattern": "^%role_id_pattern%$" + } + } + } + } + } + } + } + """ + Examples: + | permissions-role | + | Space Viewer | + | Space Editor | + | Manager | + + + Scenario Outline: invite group to project space with different roles using root endpoint + Given using spaces DAV path + And group "grp1" has been created + And the following users have been added to the following groups + | username | groupname | + | Brian | grp1 | + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And user "Alice" has created a space "NewSpace" with the default quota using the Graph API + When user "Alice" sends the following share invitation using root endpoint of the Graph API: + | space | NewSpace | + | sharee | grp1 | + | shareType | group | + | permissionsRole | | + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": { + "type": "object", + "required": [ + "grantedToV2", + "roles" + ], + "properties": { + "grantedToV2": { + "type": "object", + "required": [ + "group" + ], + "properties": { + "user": { + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "const": "grp1" + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "roles": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": { + "type": "string", + "pattern": "^%role_id_pattern%$" + } + } + } + } + } + } + } + """ + Examples: + | permissions-role | + | Space Viewer | + | Space Editor | + | Manager | + + + Scenario Outline: try to invite multiple users to project space with different roles using root endpoint + Given using spaces DAV path + And user "Carol" has been created with default attributes and without skeleton files + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And user "Alice" has created a space "NewSpace" with the default quota using the Graph API + When user "Alice" tries to send the following share invitation using root endpoint of the Graph API: + | space | NewSpace | + | sharee | Brian, Carol | + | shareType | user, user | + | permissionsRole | | + Then the HTTP status code should be "400" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["error"], + "properties": { + "error": { + "type": "object", + "required": [ + "code", + "innererror", + "message" + ], + "properties": { + "code": { + "const": "invalidRequest" + }, + "innererror": { + "type": "object", + "required": [ + "date", + "request-id" + ] + }, + "message": { + "const": "Key: 'DriveItemInvite.Recipients' Error:Field validation for 'Recipients' failed on the 'len' tag" + } + } + } + } + } + """ + Examples: + | permissions-role | + | Space Viewer | + | Space Editor | + | Manager | + + + Scenario Outline: try to invite one existing user and one non-existing user at once to project space with different roles using root endpoint + Given using spaces DAV path + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And user "Alice" has created a space "NewSpace" with the default quota using the Graph API + When user "Alice" tries to send the following share invitation using root endpoint of the Graph API: + | space | NewSpace | + | sharee | Brian, non-existent | + | shareType | user, user | + | permissionsRole | | + Then the HTTP status code should be "400" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["error"], + "properties": { + "error": { + "type": "object", + "required": [ + "code", + "innererror", + "message" + ], + "properties": { + "code": { + "const": "invalidRequest" + }, + "innererror": { + "type": "object", + "required": [ + "date", + "request-id" + ] + }, + "message": { + "const": "Key: 'DriveItemInvite.Recipients' Error:Field validation for 'Recipients' failed on the 'len' tag" + } + } + } + } + } + """ + Examples: + | permissions-role | + | Space Viewer | + | Space Editor | + | Manager | + + + Scenario Outline: try to invite multiple groups at once to project space with different roles using root endpoint + Given using spaces DAV path + And user "Carol" has been created with default attributes and without skeleton files + And group "grp1" has been created + And group "grp2" has been created + And the following users have been added to the following groups + | username | groupname | + | Brian | grp1 | + | Carol | grp2 | + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And user "Alice" has created a space "NewSpace" with the default quota using the Graph API + When user "Alice" tries to send the following share invitation using root endpoint of the Graph API: + | space | NewSpace | + | sharee | grp1, grp2 | + | shareType | group, group | + | permissionsRole | | + Then the HTTP status code should be "400" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["error"], + "properties": { + "error": { + "type": "object", + "required": [ + "code", + "innererror", + "message" + ], + "properties": { + "code": { + "const": "invalidRequest" + }, + "innererror": { + "type": "object", + "required": [ + "date", + "request-id" + ] + }, + "message": { + "const": "Key: 'DriveItemInvite.Recipients' Error:Field validation for 'Recipients' failed on the 'len' tag" + } + } + } + } + } + """ + Examples: + | permissions-role | + | Space Viewer | + | Space Editor | + | Manager | + + + Scenario Outline: try to invite one existing group and one non-existing group to project space with different roles using root endpoint + Given using spaces DAV path + And group "grp1" has been created + And the following users have been added to the following groups + | username | groupname | + | Brian | grp1 | + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And user "Alice" has created a space "NewSpace" with the default quota using the Graph API + When user "Alice" tries to send the following share invitation using root endpoint of the Graph API: + | space | NewSpace | + | sharee | grp1, grp2 | + | shareType | group, group | + | permissionsRole | | + Then the HTTP status code should be "400" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["error"], + "properties": { + "error": { + "type": "object", + "required": [ + "code", + "innererror", + "message" + ], + "properties": { + "code": { + "const": "invalidRequest" + }, + "innererror": { + "type": "object", + "required": [ + "date", + "request-id" + ] + }, + "message": { + "const": "Key: 'DriveItemInvite.Recipients' Error:Field validation for 'Recipients' failed on the 'len' tag" + } + } + } + } + } + """ + Examples: + | permissions-role | + | Space Viewer | + | Space Editor | + | Manager | + + + Scenario Outline: try to invite user and group at once to project space with different roles using root endpoint + Given using spaces DAV path + And user "Carol" has been created with default attributes and without skeleton files + And group "grp1" has been created + And the following users have been added to the following groups + | username | groupname | + | Brian | grp1 | + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And user "Alice" has created a space "NewSpace" with the default quota using the Graph API + When user "Alice" tries to send the following share invitation using root endpoint of the Graph API: + | space | NewSpace | + | sharee | Carol, grp2 | + | shareType | user, group | + | permissionsRole | | + Then the HTTP status code should be "400" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["error"], + "properties": { + "error": { + "type": "object", + "required": [ + "code", + "innererror", + "message" + ], + "properties": { + "code": { + "const": "invalidRequest" + }, + "innererror": { + "type": "object", + "required": [ + "date", + "request-id" + ] + }, + "message": { + "const": "Key: 'DriveItemInvite.Recipients' Error:Field validation for 'Recipients' failed on the 'len' tag" + } + } + } + } + } + """ + Examples: + | permissions-role | + | Space Viewer | + | Space Editor | + | Manager | + + + Scenario Outline: try to invite user to personal drive with different roles using root endpoint + When user "Alice" tries to send the following share invitation using root endpoint of the Graph API: + | space | Personal | + | sharee | Brian | + | shareType | user | + | permissionsRole | | + Then the HTTP status code should be "400" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["error"], + "properties": { + "error": { + "type": "object", + "required": [ + "code", + "innererror", + "message" + ], + "properties": { + "code": { + "const": "invalidRequest" + }, + "innererror": { + "type": "object", + "required": [ + "date", + "request-id" + ] + }, + "message": { + "const": "unsupported space type" + } + } + } + } + } + """ + Examples: + | permissions-role | + | Viewer | + | File Editor | + | Viewer | + | Editor | + | Uploader | + + + Scenario Outline: try to invite group to personal drive with different roles using root endpoint + Given group "grp1" has been created + And the following users have been added to the following groups + | username | groupname | + | Brian | grp1 | + When user "Alice" tries to send the following share invitation using root endpoint of the Graph API: + | space | Personal | + | sharee | grp1 | + | shareType | group | + | permissionsRole | | + Then the HTTP status code should be "400" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["error"], + "properties": { + "error": { + "type": "object", + "required": [ + "code", + "innererror", + "message" + ], + "properties": { + "code": { + "const": "invalidRequest" + }, + "innererror": { + "type": "object", + "required": [ + "date", + "request-id" + ] + }, + "message": { + "const": "unsupported space type" + } + } + } + } + } + """ + Examples: + | permissions-role | + | Viewer | + | File Editor | + | Viewer | + | Editor | + | Uploader | + + + Scenario Outline: try to invite user to shares drive with different re-sharing permissions using root endpoint + When user "Alice" tries to send the following share invitation using root endpoint of the Graph API: + | space | Shares | + | sharee | Brian | + | shareType | user | + | permissionsAction | | + Then the HTTP status code should be "400" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["error"], + "properties": { + "error": { + "type": "object", + "required": [ + "code", + "innererror", + "message" + ], + "properties": { + "code": { + "const": "invalidRequest" + }, + "innererror": { + "type": "object", + "required": [ + "date", + "request-id" + ] + }, + "message": { + "const": "unsupported space type" + } + } + } + } + } + """ + Examples: + | permissions-action | + | permissions/create | + | permissions/update | + | permissions/delete | + | permissions/deny | + + + Scenario Outline: try to invite group to shares drive with different re-sharing permissions using root endpoint + Given group "grp1" has been created + And the following users have been added to the following groups + | username | groupname | + | Brian | grp1 | + When user "Alice" tries to send the following share invitation using root endpoint of the Graph API: + | space | Shares | + | sharee | grp1 | + | shareType | group | + | permissionsAction | | + Then the HTTP status code should be "400" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["error"], + "properties": { + "error": { + "type": "object", + "required": [ + "code", + "innererror", + "message" + ], + "properties": { + "code": { + "const": "invalidRequest" + }, + "innererror": { + "type": "object", + "required": [ + "date", + "request-id" + ] + }, + "message": { + "const": "unsupported space type" + } + } + } + } + } + """ + Examples: + | permissions-action | + | permissions/create | + | permissions/update | + | permissions/delete | + | permissions/deny | + + + Scenario Outline: try to send a sharing invitation for the personal drive to an non-existent sharee using root endpoint + When user "Alice" tries to send the following share invitation using root endpoint of the Graph API: + | space | Personal | + | sharee | non-existent | + | shareType | | + | permissionsRole | | + Then the HTTP status code should be "400" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["error"], + "properties": { + "error": { + "type": "object", + "required": [ + "code", + "innererror", + "message" + ], + "properties": { + "code": { + "const": "invalidRequest" + }, + "innererror": { + "type": "object", + "required": [ + "date", + "request-id" + ] + }, + "message": { + "const": "unsupported space type" + } + } + } + } + } + """ + Examples: + | permissions-role | sharee-type | + | Viewer | user | + | File Editor | user | + | Viewer | user | + | Editor | user | + | Uploader | user | + | Viewer | group | + | File Editor | group | + | Viewer | group | + | Editor | group | + | Uploader | group | + + + Scenario Outline: try to send a sharing invitation for the personal drive with an empty sharee using root endpoint + When user "Alice" tries to send the following share invitation using root endpoint of the Graph API: + | space | Personal | + | sharee | | + | shareType | | + | permissionsRole | | + Then the HTTP status code should be "400" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["error"], + "properties": { + "error": { + "type": "object", + "required": [ + "code", + "innererror", + "message" + ], + "properties": { + "code": { + "const": "invalidRequest" + }, + "innererror": { + "type": "object", + "required": [ + "date", + "request-id" + ] + }, + "message": { + "const": "Key: 'DriveItemInvite.Recipients[0].ObjectId' Error:Field validation for 'ObjectId' failed on the 'ne' tag" + } + } + } + } + } + """ + Examples: + | permissions-role | sharee-type | + | Viewer | user | + | File Editor | user | + | Viewer | user | + | Editor | user | + | Uploader | user | + | Viewer | group | + | File Editor | group | + | Viewer | group | + | Editor | group | + | Uploader | group | + + + Scenario Outline: try to send a sharing invitation for the project drive to an non-existent sharee using root endpoint + Given using spaces DAV path + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And user "Alice" has created a space "NewSpace" with the default quota using the Graph API + When user "Alice" tries to send the following share invitation using root endpoint of the Graph API: + | space | NewSpace | + | sharee | non-existent | + | shareType | | + | permissionsRole | | + Then the HTTP status code should be "400" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["error"], + "properties": { + "error": { + "type": "object", + "required": [ + "code", + "innererror", + "message" + ], + "properties": { + "code": { + "const": "invalidRequest" + }, + "innererror": { + "type": "object", + "required": [ + "date", + "request-id" + ] + }, + "message": { + "const": "itemNotFound: not found" + } + } + } + } + } + """ + Examples: + | permissions-role | sharee-type | + | Space Viewer | user | + | Space Editor | user | + | Manager | user | + | Space Viewer | group | + | Space Editor | group | + | Manager | group | + + + Scenario Outline: try to send a sharing invitation for the project drive with empty sharee using root endpoint + Given using spaces DAV path + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And user "Alice" has created a space "NewSpace" with the default quota using the Graph API + When user "Alice" tries to send the following share invitation using root endpoint of the Graph API: + | space | NewSpace | + | sharee | | + | shareType | | + | permissionsRole | | + Then the HTTP status code should be "400" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["error"], + "properties": { + "error": { + "type": "object", + "required": [ + "code", + "innererror", + "message" + ], + "properties": { + "code": { + "const": "invalidRequest" + }, + "innererror": { + "type": "object", + "required": [ + "date", + "request-id" + ] + }, + "message": { + "const": "Key: 'DriveItemInvite.Recipients[0].ObjectId' Error:Field validation for 'ObjectId' failed on the 'ne' tag" + } + } + } + } + } + """ + Examples: + | permissions-role | sharee-type | + | Space Viewer | user | + | Space Editor | user | + | Manager | user | + | Space Viewer | group | + | Space Editor | group | + | Manager | group | diff --git a/tests/acceptance/features/bootstrap/SharingNgContext.php b/tests/acceptance/features/bootstrap/SharingNgContext.php index 4f7f4d9831..80fa52c967 100644 --- a/tests/acceptance/features/bootstrap/SharingNgContext.php +++ b/tests/acceptance/features/bootstrap/SharingNgContext.php @@ -628,7 +628,7 @@ class SharingNgContext implements Context { * * @return void * @throws JsonException - * @throws \GuzzleHttp\Exception\GuzzleException + * @throws GuzzleException */ public function userRemovesAccessOfUserOrGroupFromSpaceUsingGraphAPI( string $sharer, @@ -865,4 +865,63 @@ class SharingNgContext implements Context { ); $this->featureContext->setResponse($response); } + + /** + * @When /^user "([^"]*)" (?:tries to send|sends) the following share invitation using root endpoint of the Graph API:$/ + * + * @param string $user + * @param TableNode $table + * + * @return void + * @throws GuzzleException + */ + public function userSendsTheFollowingShareInvitationUsingRootEndPointTheGraphApi(string $user, TableNode $table):void { + $shareeIds = []; + $rows = $table->getRowsHash(); + if ($rows['space'] === 'Personal' || $rows['space'] === 'Shares') { + $space = $this->spacesContext->getSpaceByName($user, $rows['space']); + } else { + $space = $this->spacesContext->getCreatedSpace($rows['space']); + } + $spaceId = $space['id']; + + $sharees = array_map('trim', explode(',', $rows['sharee'])); + $shareTypes = array_map('trim', explode(',', $rows['shareType'])); + + foreach ($sharees as $index => $sharee) { + $shareType = $shareTypes[$index]; + if ($sharee === "") { + // set empty value to $shareeIds + $shareeIds[] = ""; + continue; + } + $shareeId = ""; + if ($shareType === "user") { + $shareeId = $this->featureContext->getAttributeOfCreatedUser($sharee, 'id'); + } elseif ($shareType === "group") { + $shareeId = $this->featureContext->getAttributeOfCreatedGroup($sharee, 'id'); + } + // for non-existing group or user, generate random id + $shareeIds[] = $shareeId ?: WebDavHelper::generateUUIDv4(); + } + + $permissionsRole = $rows['permissionsRole'] ?? null; + $permissionsAction = $rows['permissionsAction'] ?? null; + $expireDate = $rows["expireDate"] ?? null; + + $response = GraphHelper::sendSharingInvitationForDrive( + $this->featureContext->getBaseUrl(), + $this->featureContext->getStepLineRef(), + $user, + $this->featureContext->getPasswordForUser($user), + $spaceId, + $shareeIds, + $shareTypes, + $permissionsRole, + $permissionsAction, + $expireDate + ); + + $this->featureContext->setResponse($response); + } }