Compare commits

..
Author SHA1 Message Date
micbar 6eaa5bae63 Use omega branch for web 2026-08-06 10:32:32 +02:00
Viktor Scharf d9e406ca99 rerun flaky tests (#3183) 2026-07-28 13:44:28 +02:00
Viktor Scharf a2bdbc8b56 api-test: fix removeAccessToDrive.feature:145 (#3179)
* api-test: fix removeAccessToDrive.feature:145

* fix after review
2026-07-28 13:19:26 +02:00
Viktor Scharf 743899055a delete qa report (#3178) 2026-07-28 09:29:44 +02:00
opencloudeu f6ffd125c9 [tx] updated from transifex 2026-07-27 23:16:42 +00:00
Viktor Scharf f3693ef112 api-test: cover additional unified roles in acceptance tests (#3169) 2026-07-27 09:12:38 +02:00
opencloudeu a87163fb14 [tx] updated from transifex 2026-07-25 23:16:48 +00:00
Thomas Schweiger 2649fa0aab Merge pull request #3155 from opencloud-eu/clean-up-auth-app-docu
maint: clean-up auth-app documentation
2026-07-24 21:31:41 +02:00
Thomas Schweiger a28ddc8285 maint: clean-up auth-app documentation 2026-07-20 10:37:57 +02:00
23 changed files with 931 additions and 328 deletions

No files matched your search

-3
View File
@@ -40,9 +40,6 @@ vendor-php
suite-logs
tests/acceptance/filesForUpload/filesWithVirus/
# QA activity reports
tests/qa-activity-report/reports/
# Generated docs page (published to the server-testing-docs branch by CI)
tests/.docs-dist/
+1 -1
View File
@@ -2377,7 +2377,6 @@ def serverTestingDocs(ctx):
{
"event": ["push"],
"branch": "${CI_REPO_DEFAULT_BRANCH}",
"path": "tests/README.md",
},
],
},
@@ -2385,6 +2384,7 @@ def serverTestingDocs(ctx):
"when": [
{
"event": ["push", "pull_request"],
"path": "tests/README.md",
},
],
}]
+129 -63
View File
@@ -2,12 +2,13 @@
First, thank you for taking the time to read this and your interest in contributing to OpenCloud!
The following is a set of guidelines suitable to most of the projects hosted in the [OpenCloud Organization](https://github.com/opencloud-eu).
These are mostly guidelines, not rules.
Use your best judgment and feel free to propose changes to this document in a [pull request](https://github.com/opencloud-eu/opencloud/pulls).
For simplicity reasons, this document mostly refers to the [opencloud repository](https://www.github.com/opencloud-eu/opencloud),
but should be easily transferable to all other sub-projects.
Please also make sure to read our [AI Assisted Contribution](https://github.com/opencloud-eu/opencloud/blob/main/AI_GUIDELINE.md) guideline.
but it should be easily transferable to other (sub)projects.
#### Table Of Contents
@@ -32,6 +33,9 @@ Please also make sure to read our [AI Assisted Contribution](https://github.com/
* [Branch Naming](#branch-naming)
* [Golang Styleguide](#golang-styleguide)
[Additional Notes](#additional-notes)
* [Issue and Pull Request Labels](#issue-and-pull-request-labels)
## I don't want to read this whole thing I just have a question
> **Note:** Please don't file an issue to ask a question. You'll get faster results by using the resources below.
@@ -50,13 +54,17 @@ The OpenCloud project follows the strict GitHub workflow of development as brief
### OpenCloud Company, Engineering Partners and Community
OpenCloud is largely created by developers employed by the [OpenCloud company](https://opencloud.eu) (Germany) and by engineering partners who work full-time on related code, e.g. [REVA](https://github.com/opencloud-eu/reva/).
OpenCloud is largely created by developers who are employed by the [OpenCloud company](https://opencloud.eu), which is located in Germany.
It is providing support for OpenCloud for customers mainly in the EU. In addition, there are engineering partners who also work full-time on OpenCloud related code, for example, on the component [REVA](https://github.com/opencloud-eu/reva/).
Because of that, development can move fast for people who can't spend a comparable amount of time contributing. That shouldn't scare anyone away: we're honored by every contribution, big or small, and do our best to listen, review and consider all changes that make sense for the project.
Because of that fact, the pace that the development is moving forward is sometimes high for people who are not willing and/or able to spend a comparable amount of time to contribute.
Even though this can be a challenge, it should not scare anybody away. Here is our clear commitment that we feel honored by everybody who is interested in our work and improves it, no matter how big the contribution might be.
We as the full-time devs from either organization are doing our best to listen, review and consider all changes that are brought forward following this guideline and make sense for the project.
### Licensing and CLA
There is *no CLA* required for any of the public code of OpenCloud. The licenses are defined in every sub project.
There is *no CLA* required for any of the public code of OpenCloud.
## How to Contribute
@@ -73,67 +81,66 @@ There is no formal guideline to this, just do it :-)
### Reporting Bugs
This section guides you through submitting a bug report for OpenCloud. Following these guidelines helps maintainers and the community understand your report :pencil:, reproduce the behavior :computer:, and find related reports :mag_right:.
This section guides you through submitting a bug report for OpenCloud. Following these guidelines help maintainers and the community understand your report :pencil:,
reproduce the behavior :computer: :computer:, and find related reports :mag_right:.
Before creating a bug report:
Before creating bug reports, please check [this list](#before-submitting-a-bug-report) as you might find out that you don't need to create one.
When you are creating a bug report, please [include as many details as possible](#how-to-submit-a-good-bug-report). Fill out [the required template](https://github.com/opencloud-eu/opencloud/issues/new?Type%3ABug&template=bug_report.md), the information it asks for helps to resolve issues faster.
* **Make sure you're on a recent version.** Use the latest release or current master to reproduce the problem — that helps attract developer attention.
* **Determine which [repository](https://github.com/opencloud-eu) the problem belongs to.**
* **Search [existing issues](https://github.com/search?q=org%3Aopencloud-eu+type%3Aissue+&type=issues)** first. If an open issue matches and you have new information, comment on it instead of opening a new one — use reactions instead of "+1" comments.
> **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one. If you have permission to reopen the issue, feel free to do so.
> **Note:** If you find a **Closed** issue that seems like the same problem, open a new issue and link to the original.
#### Before Submitting A Bug Report
Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/). Create one on the right repository and fill in [the template](https://github.com/opencloud-eu/opencloud/issues/new?Type%3ABug&template=bug_report.md), covering:
* **Make sure you are running a recent version** Usually, developers' interest in old versions of software drops very fast once a new version has been released. So the general requirement is: Use the latest released version or even the current master to reproduce problems that you might encounter. That helps a lot to attract developers attention.
* **Determine which [repository](https://github.com/opencloud-eu) the problem should be reported in**.
* **Perform a [cursory search](https://github.com/search?q=org%3Aopencloud-eu+type%3Aissue+&type=issues)** with possibly a more granular filter on the repository to see if the problem has already been reported. If it has **and the issue is still open**, add a comment to the existing issue instead of opening a new one **if you have new information**. Please abstain from adding "+1" comments. Instead, use the GitHub reaction emojis to indicate that you are affected by the issue as well.
* **A clear, descriptive title.**
* **Exact reproduction steps**, from a user perspective (e.g. "I want to share pictures with Grandma") — say which client/method you used, not just what you did.
* **Specific examples**: links, files, or copy/pasteable snippets in [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines).
* **Observed vs. expected behavior.**
* **Screenshots or GIFs** demonstrating the problem (e.g. [LICEcap](https://www.cockos.com/licecap/) on macOS/Windows, [silentcast](https://github.com/colinkeenan/silentcast) or [byzanz](https://github.com/GNOME/byzanz) on Linux).
* **Browser dev tools output**, if it's a web issue.
* **Context**: did it start recently (e.g. after an update)? Can you reproduce it in an older version? Is it reliably reproducible, and under what conditions?
* **Your configuration/environment**, as requested by the template.
#### How to Submit A (Good) Bug Report
Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/). After you've determined [which repository](https://github.com/opencloud-eu) your bug is related to, create an issue on that repository and provide the following information by filling in [the template](https://github.com/opencloud-eu/opencloud/issues/new?Type%3ABug&template=bug_report.md).
Explain the problem and include additional details to help maintainers reproduce the problem:
* **Use a clear and descriptive title** for the issue to identify the problem.
* **Describe the exact steps which reproduce the problem** in as many details as possible. Start with describing, from a user perspective, what you tried to achieve, i.e. "I want to share some pictures with Grandma". When listing steps, **don't just say what you did, but explain how you did it**. For example, if you uploaded a file to OpenCloud, say which client you used, which way of uploading you chose, if the name was special somehow and how big it was.
* **Provide specific examples to demonstrate the steps**. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. If you're providing snippets in the issue, use [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines).
* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior.
* **Explain which behavior you expected to see instead and why.**
* **Include screenshots and animated GIFs** which show you following the described steps and clearly demonstrate the problem. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux.
* **If you report a web browser related problem**, consider to using the browser's Web developer tools (such as the debugger, console or network monitor) to check what happened. Make sure to add screenshots of the utilities if you are short of time to interpret it.
* **If the problem wasn't triggered by a specific action**, describe what you were doing before the problem happened and share more information using the guidelines below.
Provide more context by answering these questions:
* **Did the problem start happening recently** (e.g. after updating to a new version) or was this always a problem?
* If the problem started happening recently, **can you reproduce the problem in an older version?** What's the most recent version in which the problem doesn't happen? You can find more information about how to set up in the [Getting Started guide](https://docs.opencloud.eu/docs/admin/getting-started).
* **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens.
Include details about your configuration and environment as asked for in the template.
### Suggesting Enhancements
This section guides you through submitting an enhancement suggestion, from new features to minor improvements.
This section guides you through submitting an enhancement suggestion for OpenCloud, including completely new features and minor improvements to existing functionality.
Following these guidelines help maintainers and the community understand your suggestion :pencil: and find related suggestions :mag_right:.
Before suggesting one:
Before creating enhancement suggestions, please check [this list](#before-submitting-an-enhancement-suggestion) as you might find out that you don't need to create one.
When you are creating an enhancement suggestion, please [include as many details as possible](#how-to-submit-a-good-enhancement-suggestion).
Fill in [the template](https://github.com/opencloud-eu/opencloud/issues/new?template=feature_request.md), including the steps that you imagine you would take if the feature you're requesting existed.
* **Check whether an existing extension or component already provides it.**
* **[Search existing suggestions](https://github.com/search?q=+is%3Aissue+user%3Aopencloud)** — if found, comment or react instead of opening a new issue.
#### Before Submitting An Enhancement Suggestion
Enhancement suggestions are tracked as [GitHub issues](https://guides.github.com/features/issues/). Create one in the right repository and fill in [the template](https://github.com/opencloud-eu/opencloud/issues/new?template=feature_request.md), covering:
* **Check if there's already an extension or other component that provides that enhancement, even differently.**
* **Perform a [cursory search](https://github.com/search?q=+is%3Aissue+user%3Aopencloud)** to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one. Feel free to use the GitHub emojis to indicate that you are in favor of an enhancement request.
* **A clear, descriptive title.**
* **A step-by-step description** of the suggested enhancement.
* **Specific examples**, as [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines) where relevant.
* **Why it would be useful** to most OpenCloud users.
* **Other projects/products** where this already exists.
#### How to Submit A (Good) Enhancement Suggestion
Always try to explain the problem that you want to see solved, and why it hurts today. That opens the space for a creative solution finding.
Consider if your proposal affects many users and if it is worth to maintain it for the time being. Programming something is cheap, maintaining it is expensive.
Enhancement suggestions are tracked as [GitHub issues](https://guides.github.com/features/issues/). After you've determined [which repository](https://github.com/opencloud-eu) your enhancement suggestion is related to, create an issue on that repository and provide the following information:
### Size of the Contribution
Before submitting a PR to any repository of OpenCloud, it is important to think about its size and fit to the project. That is a big factor how maintainers look at it, especially in times of AI assisted development.
#### Bug Fixes
Bug fixes in form of "one-liners", typo fixes, fixes to translations and such are always very appreciated. Never think "that is too trivial to submit", it is not.
#### Refactorings
Bigger changes that fix misbehaviour, refactor parts of the codebase are also appreciated, but be careful. Have tests and keep changes small. Possibly ask before submitting what the maintainers think of your idea.
#### Feature Additions
Yes, we love it, but we also apply the rules here, as we take the responsibility to maintain code that we include into the project. Try to structure the changes you plan into multiple steps to make it easier to understand and review. Try to consider alternatives and document your decisions (ADR).
Most important: make sure to talk before you invest work and tokens. There is a chance that we will not take your contribution even if it is formally great because it does not fit the roadmap.
#### "Scratch your itch"
"Scratching your own itch" and pushing the results upstream is in general a great motivation to contribute to open source projects. OpenCloud supports that idea.
However, we have to keep the main direction of the project in mind, so we can not accept every "special purpose" feature.
Make sure to find a balance between functions that are good for everybody and your own needs. You will have to keep private patches for some of your additions. OpenCloud comes with a [web extension system](https://docs.opencloud.eu/de/docs/dev/web/extension-system/) to make independently maintained extensions easy.
* **Use a clear and descriptive title** for the issue to identify the suggestion.
* **Provide a step-by-step description of the suggested enhancement** in as many details as possible.
* **Provide specific examples to demonstrate the steps**. Include copy/pasteable snippets which you use in those examples, as [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines).
* **Explain why this enhancement would be useful** to most OpenCloud users.
* **List some other projects or products where this enhancement exists.**
### Your First Code Contribution
@@ -145,15 +152,11 @@ Unsure where to begin contributing to OpenCloud? You can start by looking throug
It is fine to pick one of the lists following personal preference.
While not perfect, the number of comments is a reasonable proxy for the impact a given change will have.
If you want to work on that is not yet covered by an issue, make sure to create one before you start to spend your time on implementing it. That will give you an indication if your contribution will be accepted.
Think about creating an extension for OpenCloud rather than trying to push it to the core. That makes it easier to distribute maintenance over more shoulders.
To find out how to set up OpenCloud for local development, please refer to the [Developer Documentation](https://docs.opencloud.eu/docs/dev/web/getting-started) for the web side, and the general server [README](https://github.com/opencloud-eu/opencloud/blob/main/README.md) for backend setup. Both contain information that will come in handy when starting to work on the project.
### Pull Requests
All contributions to OpenClouds projects use pull requests following the [GitHub PR workflow](https://guides.github.com/introduction/flow/).
All contributions to OpenClouds projects use so-called pull requests following the [GitHub PR workflow](https://guides.github.com/introduction/flow/).
Please follow these steps to have your contribution considered by the maintainers:
@@ -163,8 +166,6 @@ Please follow these steps to have your contribution considered by the maintainer
While the prerequisites above must be satisfied prior to having your pull request reviewed, the reviewer(s) may ask you to complete additional design work, tests, or other changes before your pull request can be ultimately accepted.
Please be patient. Silence does not mean denial, but sitting in a queue. While waiting, consider to review other PRs to speed up the process.
### Documentation Contributions
OpenCloud is very proud of the documentation it has, which is the work of a great team of people. Of course, also the documentation is open to contributions.
@@ -194,11 +195,76 @@ It is mandatory to follow them in contributions.
### Branch Naming
* Use short, descriptive, hyphen-separated lowercase names (e.g. `add-new-feature`, not `add_new_feature` or `bugfix123`).
* Avoid special characters or spaces; keep names concise, ideally under 30 characters.
* Consider including the issue number for easy reference, e.g. `issue-45-fix-login-bug`.
* Use short, descriptive names for your branches. For example, use `fix-login-bug` instead of `bugfix123`.
* Use hyphens to separate words in branch names. For example, use `add-new-feature` instead of `add_new_feature`.
* Avoid using special characters or spaces in branch names.
* Consider including the issue number in the branch name for easy reference. For example, use `issue-45-fix-login-bug` if the branch addresses issue #45.
* Keep branch names concise and to the point, ideally under 30 characters.
* Use lowercase letters to maintain consistency and avoid confusion.
### Golang Styleguide
Use the built-in golang code formatter before submitting the patch.
Also, consulting documentation like [Effective Go](https://golang.org/doc/effective_go) or [Practical Go](http://bit.ly/gcsg-2019) helps to improve the code quality.
## Additional Notes
### Issue and Pull Request Labels
This section lists the labels we use to help us track and manage issues and pull requests. Most labels are used across all OpenCloud repositories, but some are specific.
[GitHub search](https://help.github.com/articles/searching-issues/) makes it easy to use labels for finding groups of issues or pull requests you're interested in.
To help you find issues and pull requests, each label can be used in search links for finding open items with that label in the OpenCloud repositories.
The labels are loosely grouped by their purpose, but it's not required that every issue has a label from every group or that an issue can't have more than one label from the same group.
The list here contains all the more general categories of issues which are followed by a colon and a specific value.
For example, severity 1 looks like `Priority:p1-urgent`.
#### Platform
Describes the platform the issue is happening on, i.e. iOS or Windows.
#### Estimation
T-Shirt sizes for effort estimation to fix that bug or implement an enhancement. Ranges from XS to XXXL.
#### Priority
P1 to P4 (lowest) to indicate a priority. Mostly a tool for internal project management and support.
#### QA
Flags to indicate the internal QA status in terms of process and priority. Please leave alone unless you're QA ;-)
#### Severity
Severity for the product, mostly impacts on the user.
#### Type
The issue type helps to structure the issues in the agile categories (Epic, Story...) but also organizational ones.
#### Topic
A general category of the topic of a ticket.
#### Category
Categorizes the issue to also indicate the type of the issue.
#### Status
The status in the ticket life cycle. Keep an eye on that one, especially for the `Status:Needs-Review` tag which might indicate that the reporter is asked for feedback.
#### Interaction
Another label that indicates the type of the issue.
#### Browser
Important for browser-dependent web issues. It specifies the browser that shows the error.
#### Early-Adopter
Tags issues reported by one of the OpenCloud early adopters, i.e. customers and users who start using OpenCloud before its general availability.
-7
View File
@@ -346,10 +346,3 @@ vendor-bin/php_codesniffer/vendor: vendor/bamarni/composer-bin-plugin vendor-bin
vendor-bin/php_codesniffer/composer.lock: vendor-bin/php_codesniffer/composer.json
@echo php_codesniffer composer.lock is not up to date.
.PHONY: generate-qa-activity-report
generate-qa-activity-report: node_modules
@if [ -z "${MONTH}" ] || [ -z "${YEAR}" ]; then \
echo "Please set the MONTH and YEAR environment variables. Usage: make generate-qa-activity-report MONTH=<month> YEAR=<year>"; \
exit 1; \
fi
go run tests/qa-activity-report/generate-qa-activity-report.go --month ${MONTH} --year ${YEAR}
+33 -15
View File
@@ -11,7 +11,7 @@ PROXY_ENABLE_APP_AUTH=false # mandatory, disables app authentication. In ca
## App Tokens
App Tokens are password specifically generated to be used by 3rd party applications
App Tokens are passwords specifically generated to be used by 3rd party applications
for authentication when accessing the OpenCloud API endpoints. To
be able to use an app token, one must first create a token. There are different
options of creating a token.
@@ -32,16 +32,22 @@ in the `graph` service for allowing the management of App Tokens.
The `auth-app` service provides an API to create (POST), list (GET) and delete (DELETE) tokens at the `/auth-app/tokens` endpoint.
* **Create a token**\
* **Create a token**
The POST request requires:
* A `expiry` key/value pair in the form of `expiry=<number><h|m|s>`\
Example: `expiry=72h`
An `expiry` key/value pair in the form of `expiry=<number><h|m|s>`
Example: `expiry=72h`
```bash
curl --request POST 'https://<your host:9200>/auth-app/tokens?expiry={value}' \
--header 'accept: application/json'
```
Example output:
```
```bash
{
"token": "3s2K7816M4vuSpd5",
"expiration_date": "2024-08-08T13:42:42.796888022+02:00",
@@ -53,18 +59,20 @@ The `auth-app` service provides an API to create (POST), list (GET) and delete (
Note, that this is the only time the app token will be returned in cleartext. To use the token
please copy it from the response.
* **List tokens**\
* **List tokens**
```bash
curl --request GET 'https://<your host:9200>/auth-app/tokens' \
--header 'accept: application/json'
```
Note that the `token` value in the response to the "List Tokens` request is not the actual
Note that the `token` value in the response to the "List Tokens" request is not the actual
app token, but the UUID of the token. So this value cannot be used for authenticating
with the token.
Example output:
```
```bash
[
{
"token": "155f402e-1c5c-411c-92d4-92f3b612cd99"
@@ -81,10 +89,14 @@ The `auth-app` service provides an API to create (POST), list (GET) and delete (
]
```
* **Delete a token**\
* **Delete a token**
The DELETE request requires:
* A `token` key/value pair in the form of `token=<token_issued>`. The value needs to be the hashed value as returned by the `List Tokens` respone.\
A `token` key/value pair in the form of `token=<token_issued>`. The value needs to be the hashed value as returned by the `List Tokens` respone.
Example: `token=8c606bdb-e22e-4094-9304-732fd4702bc9`
```bash
curl --request DELETE 'https://<your host:9200>/auth-app/tokens?token={value}' \
--header 'accept: application/json'
@@ -101,16 +113,22 @@ reasons.
To impersonate, the respective requests from the CLI commands above extend with
the following parameters, where you can use one or the other:
* The `userID` in the form of: `userID={value}`\
Example:\
* The `userID` in the form of: `userID={value}`
Example:
`userID=4c510ada- ... -42cdf82c3d51`
* The `userName` in the form of: `userName={value}`\
Example:\
* The `userName` in the form of: `userName={value}`
Example:
`userName=alan`
Example:\
Example:
A final create request would then look like:
```bash
curl --request POST 'https://<your host:9200>/auth-app/tokens?expiry={value}&userName={value}' \
--header 'accept: application/json'
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-07-05 23:15+0000\n"
"POT-Creation-Date: 2026-07-25 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Jiri Grönroos <jiri.gronroos@iki.fi>, 2026\n"
"Language-Team: Finnish (https://app.transifex.com/opencloud-eu/teams/204053/fi/)\n"
@@ -12,7 +12,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-07-07 23:15+0000\n"
"POT-Creation-Date: 2026-07-27 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Giang Le, 2026\n"
"Language-Team: Vietnamese (https://app.transifex.com/opencloud-eu/teams/204053/vi/)\n"
+2 -2
View File
@@ -1,7 +1,7 @@
SHELL := bash
NAME := web
WEB_ASSETS_VERSION = v7.2.0
WEB_ASSETS_BRANCH = main
WEB_ASSETS_BRANCH = omega
ifneq (, $(shell command -v go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI
include ../../.bingo/Variables.mk
@@ -25,7 +25,7 @@ pull-assets:
git clean -xfd assets
@if [ ! -d tmp/assets/core/origin ]; then \
echo "no web assets found, initializing"; \
git clone -b main --depth 1 https://github.com/opencloud-eu/web.git tmp/assets/core/origin; \
git clone -b main --depth 1 https://github.com/opencloud-eu/web-internal.git tmp/assets/core/origin; \
else \
echo "web assets found, updating"; \
cd tmp/assets/core/origin; \
@@ -40,7 +40,14 @@ class GraphHelper {
public const ADDITIONAL_PERMISSIONS_ROLES = [
'Secure Viewer' => 'aa97fe03-7980-45ac-9e50-b325749fd7e6',
'Viewer With Versions' => 'd1e2218d-eef8-4d4c-b82d-0f1a1b48f3b5',
'Viewer List Grants' => 'd5041006-ebb3-4b4a-b6a4-7c180ecfb17d',
'Space Viewer With Versions' => '3de465fc-6e17-4839-8b8a-a77cc497878b',
'Editor With Versions' => 'b8c6e1c9-5d2a-4f0e-9c3b-1a2b3c4d5e6f',
'Editor List Grants' => 'e8ea8b21-abd4-45d2-b893-8d1546378e9e',
'Space Editor Without Versions' => '3284f2d5-0070-4ad8-ac40-c247f7c1fb27',
'File Editor With Versions' => '3d00ce52-1fc2-4dbc-8b95-a73b73395f5a',
'File Editor List Grants' => 'c1235aea-d106-42db-8458-7d5610fb0a67',
'Denied' => '63e64e19-8d43-42ec-a738-2b6af2610efa',
];
@@ -272,6 +272,103 @@ class FilesVersionsContext implements Context {
$this->featureContext->setResponse($response, $user);
}
/**
* @param string $user
* @param int $versionIndex
* @param string $fileId
*
* @return ResponseInterface
* @throws Exception
*/
public function restoreVersionIndexOfFileUsingFileId(
string $user,
int $versionIndex,
string $fileId
): ResponseInterface {
$user = $this->featureContext->getActualUsername($user);
$response = $this->listVersionFolder($user, $fileId, 1);
$responseXmlObject = HttpRequestHelper::getResponseXml(
$response,
__METHOD__
);
$xmlPart = $responseXmlObject->xpath("//d:response/d:href");
$destinationUrl = $this->featureContext->getBaseUrl() . "/" .
WebDavHelper::getDavPath(WebDavHelper::DAV_VERSION_SPACES, $fileId);
$fullUrl = $this->featureContext->getBaseUrlWithoutPath() .
$xmlPart[$versionIndex];
return HttpRequestHelper::sendRequest(
$fullUrl,
$this->featureContext->getStepLineRef(),
'COPY',
$user,
$this->featureContext->getPasswordForUser($user),
['Destination' => $destinationUrl]
);
}
/**
* @param string $user
* @param int $versionIndex
* @param string $fileId
*
* @return void
* @throws Exception
*/
#[When('user :user restores version index :versionIndex of file :resource using file-id :fileId')]
public function userRestoresVersionIndexOfFileUsingFileId(
string $user,
int $versionIndex,
string $fileId
): void {
$response = $this->restoreVersionIndexOfFileUsingFileId($user, $versionIndex, $fileId);
$this->featureContext->setResponse($response, $user);
}
/**
* @param string $user
* @param string $share
*
* @return void
* @throws Exception
*/
#[When('user :user gets the number of versions of shared resource :share')]
#[When('user :user tries to get the number of versions of shared resource :share')]
public function userGetsTheNumberOfVersionsOfSharedResource(string $user, string $share): void {
$fileId = $this->spacesContext->getSharesMountId($user, $share);
$this->featureContext->setResponse(
$this->featureContext->makeDavRequest(
$user,
"PROPFIND",
$fileId,
null,
null,
null,
"versions"
)
);
}
/**
* @param string $user
* @param int $versionIndex
* @param string $share
*
* @return void
* @throws Exception
*/
#[When('user :user restores version index :versionIndex of shared resource :share')]
public function userRestoresVersionIndexOfSharedResource(
string $user,
int $versionIndex,
string $share
): void {
$fileId = $this->spacesContext->getSharesMountId($user, $share);
$this->featureContext->setResponse(
$this->restoreVersionIndexOfFileUsingFileId($user, $versionIndex, $fileId),
$user
);
}
/**
* assert file versions count
*
@@ -1030,6 +1030,21 @@ class SharingNgContext implements Context {
if ($shareType == 'user' && !isset($recipient)) {
$this->featureContext->shareNgAddToCreatedUserGroupShares($this->getDrivePermissionsList($sharer, $space));
$permissionID = $this->featureContext->shareNgGetLastCreatedUserGroupShareID();
} elseif ($shareType == 'group' && !isset($recipient)) {
// https://github.com/opencloud-eu/opencloud/pull/3179#issuecomment-5103212045
$retried = 0;
do {
$response = $this->getDrivePermissionsList($sharer, $space);
$tryAgain = $response->getStatusCode() === 404
&& $retried < HttpRequestHelper::numRetriesOnHttpTooEarly();
if ($tryAgain) {
$retried += 1;
echo "Drive permissions of space '$space' not available for user '$sharer' yet, retrying ($retried)...\n";
// wait 500ms and try again
\usleep(500 * 1000);
}
} while ($tryAgain);
$permissionID = $this->featureContext->getJsonDecodedResponse($response)['value'][0]['id'];
} else {
$permissionID = match ($shareType) {
'link' => $this->featureContext->shareNgGetLastCreatedLinkShareID(),
@@ -1206,6 +1221,25 @@ class SharingNgContext implements Context {
);
}
/**
*
* @param string $user
* @param string $space
*
* @return void
* @throws JsonException
* @throws GuzzleException
*/
#[When('user :user tries to remove own group access from space :space using root endpoint of the Graph API')]
public function userRemovesOwnAccessOfGroupFromSpaceUsingGraphAPI(
string $user,
string $space
): void {
$this->featureContext->setResponse(
$this->removeAccessToSpace($user, 'group', $space)
);
}
/**
*
* @param string $user
@@ -2115,6 +2149,121 @@ class SharingNgContext implements Context {
}
}
/**
* @param string $roleId
*
* @return void
* @throws GuzzleException
*/
#[Then('the permissions roles allowed values should contain a role with id :roleId')]
public function thePermissionsRolesAllowedValuesShouldContainARoleWithId(string $roleId): void {
$responseBody = $this->featureContext->getJsonDecodedResponseBodyContent();
$allowedValues = $responseBody->{'@libre.graph.permissions.roles.allowedValues'} ?? [];
$actualIds = [];
foreach ($allowedValues as $role) {
if (isset($role->id)) {
$actualIds[] = $role->id;
}
}
Assert::assertContains(
$roleId,
$actualIds,
"Permission role id '$roleId' was not found in the allowed values. Found: " . \implode(', ', $actualIds)
);
}
/**
* @return string[] list of user ids the resource is granted to (from the last permissions response)
*/
private function getGranteeUserIdsFromResponse(): array {
$responseBody = $this->featureContext->getJsonDecodedResponseBodyContent();
$grantees = [];
foreach (($responseBody->value ?? []) as $permission) {
if (isset($permission->grantedToV2->user->id)) {
$grantees[] = $permission->grantedToV2->user->id;
}
}
return $grantees;
}
/**
* @param string $grantee
*
* @return void
* @throws GuzzleException
*/
#[Then('the permissions list of the response should contain a grant for user :grantee')]
public function thePermissionsListOfTheResponseShouldContainAGrantForUser(string $grantee): void {
$granteeId = $this->featureContext->getAttributeOfCreatedUser($grantee, 'id');
$grantees = $this->getGranteeUserIdsFromResponse();
Assert::assertContains(
$granteeId,
$grantees,
"Expected a grant for user '$grantee' ($granteeId) but found grantees: " . \implode(', ', $grantees)
);
}
/**
* @param string $grantee
*
* @return void
* @throws GuzzleException
*/
#[Then('the permissions list of the response should not contain a grant for user :grantee')]
public function thePermissionsListOfTheResponseShouldNotContainAGrantForUser(string $grantee): void {
$granteeId = $this->featureContext->getAttributeOfCreatedUser($grantee, 'id');
$grantees = $this->getGranteeUserIdsFromResponse();
Assert::assertNotContains(
$granteeId,
$grantees,
"Did not expect a grant for user '$grantee' ($granteeId) but it was present"
);
}
/**
* @param string $user
* @param string $share
*
* @return void
* @throws GuzzleException
*/
#[When('user :user gets permissions list of shared resource :share using the Graph API')]
public function userGetsPermissionsListOfSharedResourceUsingTheGraphAPI(string $user, string $share): void {
$credentials = $this->featureContext->graphContext->getAdminOrUserCredentials($user);
$sharedWithMe = GraphHelper::getSharesSharedWithMe(
$this->featureContext->getBaseUrl(),
$this->featureContext->getStepLineRef(),
$credentials['username'],
$credentials['password']
);
$jsonBody = $this->featureContext->getJsonDecodedResponseBodyContent($sharedWithMe);
$driveId = null;
$itemId = null;
foreach ($jsonBody->value as $item) {
if (isset($item->name) && $item->name === $share && isset($item->remoteItem->id)) {
$itemId = $item->remoteItem->id;
$driveId = $item->remoteItem->parentReference->driveId;
break;
}
}
Assert::assertNotNull(
$itemId,
"Cannot find shared resource '$share' in the shared-with-me list of user '$user'"
);
$this->featureContext->setResponse(
GraphHelper::getPermissionsList(
$this->featureContext->getBaseUrl(),
$this->featureContext->getStepLineRef(),
$credentials['username'],
$credentials['password'],
$driveId,
$itemId
)
);
}
/**
*
* @param string $user
@@ -302,6 +302,37 @@ class SpacesContext implements Context {
throw new Exception("Cannot find share: $share");
}
/**
* @param string $user
* @param string $share
*
* @return string
*
* @throws Exception|GuzzleException
*/
public function getSharesMountId(string $user, string $share): string {
$credentials = $this->featureContext->graphContext->getAdminOrUserCredentials($user);
$response = GraphHelper::getSharesSharedWithMe(
$this->featureContext->getBaseUrl(),
$this->featureContext->getStepLineRef(),
$credentials['username'],
$credentials['password']
);
$jsonBody = $this->featureContext->getJsonDecodedResponseBodyContent($response);
foreach ($jsonBody->value as $item) {
if (isset($item->name) && $item->name === $share) {
if (isset($item->id)) {
return $item->id;
}
throw new Exception("Failed to find mount ID for share: $share");
}
}
throw new Exception("Cannot find share: $share");
}
/**
* The method finds file by fileName and spaceName and returns data of file which contains in responseHeader
* fileName contains the path, if the file is in the folder
+2
View File
@@ -326,6 +326,7 @@ default:
- SharingNgContext:
- OcConfigContext:
- SettingsContext:
- FilesVersionsContext:
apiSharingNg2:
paths:
@@ -355,6 +356,7 @@ default:
- FeatureContext: *common_feature_context_params
- SpacesContext:
- SharingNgContext:
- OcConfigContext:
apiSharingNgLinkSharePermission:
paths:
@@ -339,16 +339,17 @@ _ocdav: api compatibility, return correct status code_
- [coreApiWebdavPreviews/previews.feature:265](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L265)
#### [500 when user tries to delete group from space](https://github.com/opencloud-eu/opencloud/issues/2799)
- [apiSharingNg1/removeAccessToDrive.feature:145](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSharingNg1/removeAccessToDrive.feature#L145)
#### [POSIX: failing CLI commands](https://github.com/opencloud-eu/opencloud/issues/3095)
- [cliCommands/restoreTrashBinItems.feature:11](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/cliCommands/restoreTrashBinItems.feature#L11)
- [cliCommands/restoreTrashBinItems.feature:23](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/cliCommands/restoreTrashBinItems.feature#L23)
- [cliCommands/sharesCleanup.feature:12](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/cliCommands/sharesCleanup.feature#L12)
#### [user with roleFileEditorWithVersions permission cannot restore version of file](https://github.com/opencloud-eu/opencloud/issues/3168)
- [apiSharingNg1/shareFileVersions.feature:55](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSharingNg1/shareFileVersions.feature#L55)
- [apiSharingNg1/shareFileVersions.feature:56](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSharingNg1/shareFileVersions.feature#L56)
### Won't fix
Not everything needs to be implemented for opencloud.
@@ -342,10 +342,6 @@ _ocdav: api compatibility, return correct status code_
- [coreApiWebdavPreviews/previews.feature:264](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L264)
- [coreApiWebdavPreviews/previews.feature:265](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L265)
### TODO: adjust test
- [apiSharingNg1/removeAccessToDrive.feature:145](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSharingNg1/removeAccessToDrive.feature#L145)
tests/acceptance/features/cliCommands/restoreTrashBinItems.feature
#### [POSIX: failing CLI commands](https://github.com/opencloud-eu/opencloud/issues/3095)
@@ -354,6 +350,10 @@ tests/acceptance/features/cliCommands/restoreTrashBinItems.feature
- [cliCommands/restoreTrashBinItems.feature:23](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/cliCommands/restoreTrashBinItems.feature#L23)
- [cliCommands/sharesCleanup.feature:12](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/cliCommands/sharesCleanup.feature#L12)
#### [user with roleFileEditorWithVersions permission cannot restore version of file](https://github.com/opencloud-eu/opencloud/issues/3168)
- [apiSharingNg1/shareFileVersions.feature:55](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSharingNg1/shareFileVersions.feature#L55)
- [apiSharingNg1/shareFileVersions.feature:56](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSharingNg1/shareFileVersions.feature#L56)
### Won't fix
@@ -418,7 +418,7 @@ Feature: enable or disable sync of incoming shares
}
"""
@flaky @issue-1878
Scenario: try to enable sync of shared resource from Personal Space when sharer is deleted
Given user "Brian" has disabled the auto-sync share
And user "Alice" has uploaded file with content "hello world" to "/textfile0.txt"
@@ -6,6 +6,64 @@ Feature: List a sharing permissions
| username |
| Alice |
@env-config
Scenario Outline: enabled additional role is listed in the permissions of a folder
Given the administrator has enabled the permissions role "<permissions-role>"
And user "Alice" has created folder "folder"
When user "Alice" gets permissions list for folder "folder" of the space "Personal" using the Graph API
Then the HTTP status code should be "200"
And the permissions roles allowed values should contain a role with id "<role-id>"
Examples:
| permissions-role | role-id |
| Viewer With Versions | d1e2218d-eef8-4d4c-b82d-0f1a1b48f3b5 |
| Viewer List Grants | d5041006-ebb3-4b4a-b6a4-7c180ecfb17d |
| Editor With Versions | b8c6e1c9-5d2a-4f0e-9c3b-1a2b3c4d5e6f |
| Editor List Grants | e8ea8b21-abd4-45d2-b893-8d1546378e9e |
@env-config
Scenario Outline: enabled additional role is listed in the permissions of a file
Given the administrator has enabled the permissions role "<permissions-role>"
And user "Alice" has uploaded file with content "hello" to "textfile.txt"
When user "Alice" gets permissions list for file "textfile.txt" of the space "Personal" using the Graph API
Then the HTTP status code should be "200"
And the permissions roles allowed values should contain a role with id "<role-id>"
Examples:
| permissions-role | role-id |
| Viewer With Versions | d1e2218d-eef8-4d4c-b82d-0f1a1b48f3b5 |
| Viewer List Grants | d5041006-ebb3-4b4a-b6a4-7c180ecfb17d |
| File Editor With Versions | 3d00ce52-1fc2-4dbc-8b95-a73b73395f5a |
| File Editor List Grants | c1235aea-d106-42db-8458-7d5610fb0a67 |
@env-config
Scenario Outline: sharee with a list-grants role can see the other grants of a shared resource
Given user "Brian" has been created with default attributes
And user "Carol" has been created with default attributes
And the administrator has enabled the permissions role "<permissions-role>"
And user "Alice" has uploaded file with content "hello" to "textfile.txt"
And user "Alice" has sent the following resource share invitation:
| resource | textfile.txt |
| space | Personal |
| sharee | Brian |
| shareType | user |
| permissionsRole | <permissions-role> |
And user "Alice" has sent the following resource share invitation:
| resource | textfile.txt |
| space | Personal |
| sharee | Carol |
| shareType | user |
| permissionsRole | Viewer |
And user "Brian" has a share "textfile.txt" synced
When user "Brian" gets permissions list of shared resource "textfile.txt" using the Graph API
Then the HTTP status code should be "200"
And the permissions list of the response should contain a grant for user "Carol"
When user "Carol" gets permissions list of shared resource "textfile.txt" using the Graph API
Then the HTTP status code should be "200"
And the permissions list of the response should not contain a grant for user "Brian"
Examples:
| permissions-role |
| Viewer List Grants |
| File Editor List Grants |
Scenario: user lists permissions of a folder in personal space
Given user "Alice" has created folder "folder"
@@ -2345,6 +2403,104 @@ Feature: List a sharing permissions
}
"""
@env-config
Scenario: user lists permissions of a space after enabling 'Space Viewer With Versions' role
Given the administrator has enabled the permissions role "Space Viewer With Versions"
And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API
And user "Alice" has created a space "new-space" with the default quota using the Graph API
When user "Alice" lists the permissions of space "new-space" using root endpoint of the Graph API
Then the HTTP status code should be "200"
And the JSON data of the response should match
"""
{
"type": "object",
"required": [
"@libre.graph.permissions.actions.allowedValues",
"@libre.graph.permissions.roles.allowedValues"
],
"properties": {
"@libre.graph.permissions.roles.allowedValues": {
"type": "array",
"minItems": 4,
"maxItems": 4,
"uniqueItems": true,
"items": {
"oneOf": [
{
"type": "object",
"required": [
"@libre.graph.weight",
"description",
"displayName",
"id"
],
"properties": {
"displayName": {
"const": "Can view"
},
"id": {
"const": "a8d5fe5e-96e3-418d-825b-534dbdf22b99"
}
}
},
{
"type": "object",
"required": [
"@libre.graph.weight",
"description",
"displayName",
"id"
],
"properties": {
"@libre.graph.weight": {
"const": 40
},
"description": {
"const": "View and download including the history."
},
"displayName": {
"const": "Can view"
},
"id": {
"const": "3de465fc-6e17-4839-8b8a-a77cc497878b"
}
}
},
{
"type": "object",
"required": [
"@libre.graph.weight",
"description",
"displayName",
"id"
],
"properties": {
"displayName": {
"const": "Can edit"
}
}
},
{
"type": "object",
"required": [
"@libre.graph.weight",
"description",
"displayName",
"id"
],
"properties": {
"displayName": {
"const": "Can manage"
}
}
}
]
}
}
}
}
"""
@env-config
Scenario: user lists permissions of a folder after enabling 'Denied' role
Given the administrator has enabled the permissions role "Denied"
@@ -153,7 +153,7 @@ Feature: Remove access to a drive
| shareType | group |
| permissionsRole | Manager |
And user "Alice" has removed own access from space "NewSpace"
When user "Brian" tries to remove the access of group "group1" from space "NewSpace" using root endpoint of the Graph API
When user "Brian" tries to remove own group access from space "NewSpace" using root endpoint of the Graph API
Then the HTTP status code should be "403"
And the user "Brian" should have a space called "NewSpace"
@@ -0,0 +1,56 @@
Feature: file versions of a shared resource
As a user
I want to access versions of a resource shared with me
So that I can view and manage its history according to my role
# These scenarios address the shared file the same way a web client does: by the
# Shares-mount id resolved from the sharee's shared-with-me list (not by the owner's
# storage resource id), so that regressions of the sharee code path are caught.
Background:
Given these users have been created with default attributes:
| username |
| Alice |
| Brian |
And user "Alice" has uploaded file with content "hello world version 1" to "text.txt"
And user "Alice" has uploaded file with content "hello world version 1.1" to "text.txt"
@env-config
Scenario Outline: sharee can view file versions of a shared file while upload and restore depend on the role
Given the administrator has enabled the permissions role "<role>"
And user "Alice" has sent the following resource share invitation:
| resource | text.txt |
| space | Personal |
| sharee | Brian |
| shareType | user |
| permissionsRole | <role> |
And user "Brian" has a share "text.txt" synced
When user "Brian" gets the number of versions of shared resource "text.txt"
Then the HTTP status code should be "207"
And the number of versions should be "1"
When user "Brian" uploads file with content "shared file new version" to "/Shares/text.txt" using the WebDAV API
Then the HTTP status code should be "<upload-code>"
Examples:
| role | upload-code |
| Viewer With Versions | 403 |
| File Editor With Versions | 204 |
@env-config @issue-3168
Scenario Outline: sharee tries to restore file version
Given the administrator has enabled the permissions role "<role>"
And user "Alice" has sent the following resource share invitation:
| resource | text.txt |
| space | Personal |
| sharee | Brian |
| shareType | user |
| permissionsRole | <role> |
And user "Brian" has a share "text.txt" synced
When user "Brian" gets the number of versions of shared resource "text.txt"
Then the HTTP status code should be "207"
And the number of versions should be "1"
When user "Brian" restores version index "1" of shared resource "text.txt"
Then the HTTP status code should be "<restore-code>"
Examples:
| role | restore-code |
| Viewer With Versions | 403 |
| File Editor With Versions | 204 |
@@ -97,6 +97,95 @@ Feature: Send a sharing invitations
| Editor | FolderToShare |
| Uploader | FolderToShare |
@env-config
Scenario Outline: send share invitation to user with the disabled-by-default roles
Given the administrator has enabled the permissions role "<permissions-role>"
And user "Alice" has uploaded file with content "to share" to "/textfile1.txt"
And user "Alice" has created folder "FolderToShare"
When user "Alice" sends the following resource share invitation using the Graph API:
| resource | <resource> |
| space | Personal |
| sharee | Brian |
| shareType | user |
| permissionsRole | <permissions-role> |
Then the HTTP status code should be "200"
And user "Brian" has a share "<resource>" synced
And user "Brian" should have a share "<resource>" shared by user "Alice" from space "Personal"
And the JSON data of the response should match
"""
{
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"maxItems": 1,
"minItems": 1,
"items": {
"type": "object",
"required": [
"createdDateTime",
"id",
"roles",
"grantedToV2"
],
"properties": {
"id": {
"type": "string",
"pattern": "^%permissions_id_pattern%$"
},
"roles": {
"type": "array",
"maxItems": 1,
"minItems": 1,
"items": {
"type": "string",
"pattern": "^%role_id_pattern%$"
}
},
"grantedToV2": {
"type": "object",
"required": [
"user"
],
"properties": {
"user": {
"type": "object",
"required": [
"id",
"displayName"
],
"properties": {
"id": {
"type": "string",
"pattern": "^%user_id_pattern%$"
},
"displayName": {
"const": "Brian Murphy"
}
}
}
}
}
}
}
}
}
}
"""
Examples:
| permissions-role | resource |
| Viewer With Versions | /textfile1.txt |
| Viewer With Versions | FolderToShare |
| File Editor With Versions | /textfile1.txt |
| Editor With Versions | FolderToShare |
| Viewer List Grants | /textfile1.txt |
| Viewer List Grants | FolderToShare |
| File Editor List Grants | /textfile1.txt |
| Editor List Grants | FolderToShare |
Scenario Outline: send share invitation to group with different roles
Given user "Carol" has been created with default attributes
@@ -3,6 +3,22 @@ Feature: checking file versions using file id
I want the versions of files to be available
So that I can manage the changes made to the files
role view upload restore
Viewer no no no
Viewer With Versions yes no no
File Editor no yes no
File Editor With Versions yes yes yes
Editor no yes no
Editor With Versions yes yes yes
Space Viewer no no no
Space Viewer With Versions yes no no
Space Editor Without Versions no yes no
Space Editor yes yes yes
Manager yes yes yes
Background:
Given these users have been created with default attributes:
| username |
@@ -62,6 +78,46 @@ Feature: checking file versions using file id
When user "Brian" tries to get the number of versions of file "/text.txt" using file-id "<<FILEID>>"
Then the HTTP status code should be "403"
@env-config
Scenario: sharee can view file versions in a shared space as space viewer with versions role
Given the administrator has enabled the permissions role "Space Viewer With Versions"
And user "Alice" has sent the following space share invitation:
| space | Project1 |
| sharee | Brian |
| shareType | user |
| permissionsRole | Space Viewer With Versions |
When user "Brian" gets the number of versions of file "/text.txt" using file-id "<<FILEID>>"
Then the HTTP status code should be "207"
And the number of versions should be "1"
When user "Brian" restores version index "1" of file "/text.txt" using file-id "<<FILEID>>"
Then the HTTP status code should be "403"
@env-config
Scenario Outline: sharee can view file versions of a file inside a shared folder while upload and restore depend on the role
Given the administrator has enabled the permissions role "<role>"
And user "Alice" has created a folder "folderToShare" in space "Project1"
And user "Alice" has uploaded a file inside space "Project1" with content "folder file version 1" to "folderToShare/insideText.txt"
And user "Alice" has uploaded a file inside space "Project1" with content "folder file version 1.1" to "folderToShare/insideText.txt"
And we save it into "FOLDERFILEID"
And user "Alice" has sent the following resource share invitation:
| resource | folderToShare |
| space | Project1 |
| sharee | Brian |
| shareType | user |
| permissionsRole | <role> |
And user "Brian" has a share "folderToShare" synced
When user "Brian" gets the number of versions of file "/Shares/folderToShare/insideText.txt" using file-id "<<FOLDERFILEID>>"
Then the HTTP status code should be "207"
And the number of versions should be "1"
When user "Brian" uploads file with content "folder file new version" to "/Shares/folderToShare/insideText.txt" using the WebDAV API
Then the HTTP status code should be "<upload-code>"
When user "Brian" restores version index "1" of file "/Shares/folderToShare/insideText.txt" using file-id "<<FOLDERFILEID>>"
Then the HTTP status code should be "<restore-code>"
Examples:
| role | upload-code | restore-code |
| Viewer With Versions | 403 | 403 |
| Editor With Versions | 204 | 204 |
@issue-7738
Scenario Outline: check the versions of a file after moving to a shared folder inside a project space as editor/viewer
Given user "Alice" has created a folder "testFolder" in space "Project1"
+110
View File
@@ -191,6 +191,12 @@ then
BEHAT_RERUN_TIMES=1
fi
# rerun a failed scenario that is tagged @flaky.
if [ -z "${BEHAT_FLAKY_RERUN_TIMES}" ]
then
BEHAT_FLAKY_RERUN_TIMES=1
fi
# expected variables
# --------------------
# $SUITE_FEATURE_TEXT - human readable which test to run
@@ -211,6 +217,7 @@ fi
declare -a UNEXPECTED_FAILED_SCENARIOS
declare -a UNEXPECTED_PASSED_SCENARIOS
declare -a UNEXPECTED_BEHAT_EXIT_STATUSES
declare -a ALL_FAILED_SCENARIO_PATHS
function run_behat_tests() {
echo "Running ${SUITE_FEATURE_TEXT} tests tagged ${BEHAT_FILTER_TAGS}" | tee ${TEST_LOG_FILE}
@@ -297,6 +304,11 @@ function run_behat_tests() {
if [ -z "${FAILED_SCENARIO_PATHS}" ]
then
unset FAILED_SCENARIO_PATHS
else
for FAILED_SCENARIO_PATH in ${FAILED_SCENARIO_PATHS}
do
ALL_FAILED_SCENARIO_PATHS+=("${FAILED_SCENARIO_PATH}")
done
fi
if [ -n "${EXPECTED_FAILURES_FILE}" ]
@@ -403,6 +415,101 @@ function run_behat_tests() {
fi
}
# Retry scenarios that failed but are tagged @flaky.
function rerun_failed_flaky_scenarios() {
if [ ${BEHAT_FLAKY_RERUN_TIMES} -lt 1 ]
then
return
fi
if [ ${#UNEXPECTED_FAILED_SCENARIOS[@]} -eq 0 ] || [ ${#ALL_FAILED_SCENARIO_PATHS[@]} -eq 0 ]
then
return
fi
# suite/scenario keys that failed first but passed on a retry
local recovered=()
for FEATURE_PATH in "${ALL_FAILED_SCENARIO_PATHS[@]}"
do
local suite scenario key
suite=$(basename "$(dirname "${FEATURE_PATH}")")
scenario=$(basename "${FEATURE_PATH}")
key="${suite}/${scenario}"
local is_unexpected=false
for uf in "${UNEXPECTED_FAILED_SCENARIOS[@]}"
do
if [ "${uf}" = "${key}" ]
then
is_unexpected=true
break
fi
done
if [ "${is_unexpected}" != true ]
then
continue
fi
local dry_run_file
dry_run_file=$(mktemp)
${BEHAT} --dry-run --no-colors -c ${BEHAT_YML} -f pretty --tags '@flaky' "${FEATURE_PATH}" 1>${dry_run_file} 2>/dev/null
if grep -q -m 1 'No scenarios' "${dry_run_file}"
then
rm -f "${dry_run_file}"
continue
fi
rm -f "${dry_run_file}"
log_info "Scenario ${key} is tagged @flaky, retrying up to ${BEHAT_FLAKY_RERUN_TIMES} times"
local passed=false
for attempt in $(seq 1 ${BEHAT_FLAKY_RERUN_TIMES})
do
echo -e "\nFlaky retry attempt ${attempt} of ${BEHAT_FLAKY_RERUN_TIMES}: ${key}"
${BEHAT} ${COLORS_OPTION} --strict -c ${BEHAT_YML} -f pretty --tags '@flaky' "${FEATURE_PATH}" -v 2>&1 | tee -a ${TEST_LOG_FILE}
if [ ${PIPESTATUS[0]} -eq 0 ]
then
passed=true
break
fi
done
if [ "${passed}" = true ]
then
log_info "Flaky scenario ${key} passed on retry, not counting it as a failure."
recovered+=("${key}")
else
log_failed "Flaky scenario ${key} still failing after ${BEHAT_FLAKY_RERUN_TIMES} retries."
fi
done
if [ ${#recovered[@]} -eq 0 ]
then
return
fi
# Drop the recovered flaky scenarios from the list of unexpected failures.
local remaining=()
for uf in "${UNEXPECTED_FAILED_SCENARIOS[@]}"
do
local drop=false
for r in "${recovered[@]}"
do
if [ "${uf}" = "${r}" ]
then
drop=true
break
fi
done
if [ "${drop}" != true ]
then
remaining+=("${uf}")
fi
done
UNEXPECTED_FAILED_SCENARIOS=("${remaining[@]}")
}
declare -x TEST_SERVER_URL
if [ -z "${IPV4_URL}" ]
@@ -590,6 +697,9 @@ for i in "${!BEHAT_SUITES[@]}"
done
done
# Give scenarios tagged @flaky another chance before treating them as failures.
rerun_failed_flaky_scenarios
# 3 types of things can have gone wrong:
# - some scenario failed (and it was not expected to fail)
# - some scenario passed (but it was expected to fail)
@@ -1,225 +0,0 @@
package main
import (
"bytes"
"encoding/csv"
"fmt"
"os"
"os/exec"
"strconv"
"strings"
"time"
)
func main() {
since, until, period, err := getTimeframe()
if err != nil {
fmt.Println("Error:", err)
return
}
logs, err := getGitLog(since, until)
if err != nil {
fmt.Println("Error:", err)
return
}
csvRows := make([][]string, 0, 1000)
for _, logLine := range logs {
logParts := strings.Split(logLine, " ")
if len(logParts) < 2 {
continue
}
commit := logParts[0]
date := logParts[1]
diffLines, err := getGitDiff(commit)
if err != nil {
fmt.Println("Error:", err)
return
}
addedTests, changedTests, deletedTests := 0, 0, 0
// var inScenarios bool
for i, line := range diffLines {
switch {
case strings.HasPrefix(line, "+") && !strings.HasPrefix(line, "+++"):
if strings.Contains(line, "Scenario:") {
addedTests++
} else if strings.Contains(line, "Scenario Outline:") {
addedTests += countAddedTestsInExamples(diffLines, i)
}
case strings.HasPrefix(line, "-") && !strings.HasPrefix(line, "---"):
if strings.Contains(line, "Scenario") {
deletedTests++
}
case strings.Contains(line, "@@ Feature:"):
changedTests := 0
for i, line := range diffLines {
if strings.Contains(line, "@@ Feature:") {
inScenarios, changed := checkChangedTests(diffLines, i)
if !inScenarios {
changedTests += changed
}
}
}
}
}
csvRows = append(csvRows, []string{"API Test", date, strconv.Itoa(addedTests), strconv.Itoa(changedTests), strconv.Itoa(deletedTests), commit})
}
// Ensure the directory exists
reportDir := "tests/qa-activity-report/reports"
if err := os.MkdirAll(reportDir, os.ModePerm); err != nil {
fmt.Println("Error creating directory:", err)
return
}
// Define the path for the CSV file
filePath := fmt.Sprintf("%s/QA_Activity_Report_%s.csv", reportDir, period)
if err := generateCSV(csvRows, filePath); err != nil {
fmt.Println("Error writing CSV report:", err)
} else {
fmt.Println("CSV report generated successfully. You can find it in", filePath)
}
}
func getTimeframe() (since string, until string, period string, err error) {
monthStr := os.Getenv("MONTH")
yearStr := os.Getenv("YEAR")
daysStr := os.Getenv("DAYS")
if monthStr != "" && yearStr != "" {
month, err := strconv.Atoi(monthStr)
if err != nil {
return "", "", "", fmt.Errorf("invalid month: %w", err)
}
year, err := strconv.Atoi(yearStr)
if err != nil {
return "", "", "", fmt.Errorf("invalid year: %w", err)
}
startDate := time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.UTC)
endDate := startDate.AddDate(0, 1, -1)
since = startDate.Format("2006-01-02")
until = endDate.Format("2006-01-02")
period = fmt.Sprintf("%02d_%04d", month, year)
} else if daysStr != "" {
days, err := strconv.Atoi(daysStr)
if err != nil {
return "", "", "", fmt.Errorf("invalid number of days: %w", err)
}
until = time.Now().Format("2006-01-02")
since = time.Now().AddDate(0, 0, -days).Format("2006-01-02")
period = fmt.Sprintf("Last_%d_days", days)
} else {
return "", "", "", fmt.Errorf("please provide either MONTH and YEAR or DAYS")
}
return since, until, period, nil
}
func getGitLog(since, until string) ([]string, error) {
cmd := exec.Command("git", "log", "--since="+since, "--until="+until, "--pretty=format:%H %ad", "--date=short", "--", "tests/acceptance/features", ":(exclude)tests/acceptance/features/bootstrap/")
var out bytes.Buffer
cmd.Stdout = &out
if err := cmd.Run(); err != nil {
return nil, err
}
return strings.Split(out.String(), "\n"), nil
}
func getGitDiff(commit string) ([]string, error) {
cmd := exec.Command("git", "diff", commit+"~1", commit, "--", "tests/acceptance/features", ":(exclude)tests/acceptance/features/bootstrap/")
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return nil, err
}
return strings.Split(out.String(), "\n"), nil
}
func countAddedTestsInExamples(diffLines []string, startIndex int) int {
var inExamples bool
addedTests := 0
for j := startIndex + 1; j < len(diffLines); j++ {
exampleLine := diffLines[j]
if strings.HasPrefix(exampleLine, "+") && strings.Contains(exampleLine, "Examples:") {
inExamples = true
continue
} else if inExamples {
trimmedLine := strings.TrimSpace(exampleLine)
if strings.HasPrefix(trimmedLine, "+") && strings.Contains(trimmedLine, "|") {
// Count a string if it starts with "+" and contains "|"
addedTests++
} else if strings.TrimSpace(exampleLine) == "" || !strings.HasPrefix(trimmedLine, "+") || !strings.HasPrefix(trimmedLine, "|") {
// Abort counting when a row that does not belong to the table is encountered
break
}
}
}
// We have one line | resource | which is not a test line. So we deleted one line from addedTest
// Examples:
// | resource |
// | testfile.txt |
// | FolderToShare |
if inExamples {
addedTests--
}
return addedTests
}
func checkChangedTests(diffLines []string, startIndex int) (bool, int) {
var inScenarios bool
changedTests := 0
for j := startIndex + 1; j < len(diffLines); j++ {
scenarioLine := diffLines[j]
if strings.HasPrefix(scenarioLine, "+") || strings.HasPrefix(scenarioLine, "-") {
// If there are changes and the string contains the word "Scenario", set inScenarios to true
if strings.Contains(scenarioLine, "Scenario") {
inScenarios = true
break
}
// If the line no longer starts with "-" "+", then the change block has ended
if !strings.HasPrefix(scenarioLine, "-") && !strings.HasPrefix(scenarioLine, "+") {
break
}
}
}
// If we didn't find "Scenario" in the changes, increase the changedTests counter
if !inScenarios {
changedTests++
}
return inScenarios, changedTests
}
func generateCSV(csvRows [][]string, filePath string) error {
file, err := os.Create(filePath)
if err != nil {
return err
}
defer file.Close()
writer := csv.NewWriter(file)
defer writer.Flush()
header := []string{"Test-Type", "Date", "Tests Added", "Tests Changed", "Tests Deleted", "commit-ID"}
if err := writer.Write(header); err != nil {
return err
}
for _, row := range csvRows {
if err := writer.Write(row); err != nil {
return err
}
}
return nil
}