Merge remote-tracking branch 'origin/release/12.5' into dev

This commit is contained in:
Oliver Günther
2023-04-24 13:29:45 +02:00
94 changed files with 1296 additions and 166 deletions
+3 -3
View File
@@ -60,9 +60,9 @@ module Projects
# prevent adding another error if there is already one present
return if errors.present?
subprojects = model.descendants
return if subprojects.empty?
return if user.allowed_to?(:archive_project, subprojects)
active_subprojects = model.active_subprojects
return if active_subprojects.empty?
return if user.allowed_to?(:archive_project, active_subprojects)
errors.add :base, :archive_permission_missing_on_subprojects
end
+5
View File
@@ -342,6 +342,11 @@ class Project < ApplicationRecord
parents | descendants # Set union
end
# Returns an array of active subprojects.
def active_subprojects
project.descendants.where(active: true)
end
class << self
# builds up a project hierarchy helper structure for use with #project_tree_from_hierarchy
#
+2 -2
View File
@@ -39,8 +39,8 @@ module Projects
private
def persist(service_call)
archive_project(model) and model.children.each do |child|
archive_project(child)
archive_project(model) and model.active_subprojects.each do |subproject|
archive_project(subproject)
end
service_call
+5 -1
View File
@@ -104,7 +104,7 @@ module Users
# Try to register a user with an existsing omniauth connection
# bypassing regular account registration restrictions
def register_omniauth_user
return if user.identity_url.blank?
return if skip_omniauth_user?
user.activate
@@ -113,6 +113,10 @@ module Users
end
end
def skip_omniauth_user?
user.identity_url.blank?
end
def register_by_email_activation
return unless Setting::SelfRegistration.by_email?
+14 -2
View File
@@ -57,12 +57,24 @@ elif [[ "$1" = "setup" ]]; then
elif [[ "$1" = "reset" ]]; then
$DOCKER_COMPOSE -f $COMPOSE_FILE down && docker volume rm `docker volume ls -q | grep ${PWD##*/}_`
elif [[ "$1" = "rspec" ]]; then
if ! docker ps | grep ${PWD##*/}_backend-test_1 > /dev/null; then
function get-container-name() {
name=`$DOCKER_COMPOSE ps backend-test | tail -n1 | cut -d ' ' -f1`
if [ "$name" = 'NAME' ]; then
return 1;
else
echo "$name"
fi
}
if ! get-container-name > /dev/null; then
echo "Test backend not running yet. Starting it..."
$DOCKER_COMPOSE -f $COMPOSE_FILE up -d backend-test
while ! docker logs --since 1m ${PWD##*/}_backend-test_1 | grep "Ready for tests" > /dev/null; do
container=`get-container-name`
while ! docker logs --since 1m $container 2>&1 | grep "Ready for tests" &> /dev/null; do
sleep 1
printf "."
done
@@ -12,7 +12,7 @@ class RestoreDefaultsOnEmptySettings < ActiveRecord::Migration[6.1]
next if definition.value == ''
setting.update_column(:value, definition.value)
setting.update_attribute(:value, definition.value)
end
end
+1 -1
View File
@@ -44,7 +44,7 @@ x-op-backend: &backend
OPENPROJECT_RAILS__CACHE__STORE: file_store
OPENPROJECT_RAILS__RELATIVE__URL__ROOT: "${OPENPROJECT_RAILS__RELATIVE__URL__ROOT:-}"
DATABASE_URL: postgresql://${DB_USERNAME:-postgres}:${DB_PASSWORD:-postgres}@${DB_HOST:-db}:${DB_PORT:-5432}/${DB_DATABASE:-openproject}
OPENPROJECT_EDITION: $OPENPROJECT_EDITION
OPENPROJECT_EDITION: ${OPENPROJECT_EDITION:-standard}
volumes:
- ".:/home/dev/openproject"
- "opdata:/var/openproject/assets"
@@ -198,11 +198,16 @@ If you _really_ want to disable HSTS headers and request upgrades, you will need
For more advanced configuration, please have a look at the [Advanced configuration](../../configuration) section.
### Apache Reverse Proxy Setup
### Reverse Proxy Setup
The containers above are not meant as public facing endpoints. Always use an existing proxying web server or load balancer to provide access to OpenProject
The containers above are not meant as public facing endpoints.
Always use an existing proxying web server or load balancer to provide access to OpenProject.
There are two ways to run OpenProject. We'll cover each configuration in a separate of the following sections.
Moreover we're going to give basic configurations for both the [Apache](https://httpd.apache.org/)
and [nginx](https://nginx.org/en/) web servers.
**Apache**
For both configurations the following Apache mods are required:
@@ -214,14 +219,21 @@ For both configurations the following Apache mods are required:
In each case you will create a file `/usr/local/apache2/conf/sites/openproject.conf`
with the contents as described in the respective sections.
Both configuration examples are based on the following assumptions:
**Nginx**
The nginx configuration will go into `/etc/nginx/conf.d/openproject.conf`.
**Assumptions**
All examples are based on the following assumptions:
* the site is accessed via https
* certificate and key are located under `/etc/ssl/crt/server.{crt, key}`
* the OpenProject docker container's port 80 is mapped to the docker host's port 8080
*Important:* Once OpenProject is running make sure to also set the host name and protocol
accordingly under Administration -> System Settings.
*Important:* Once OpenProject is running make sure to also set the host name accordingly under Administration -> System Settings or set it directly during startup by setting `OPENPROJECT_HOST__NAME`.
> **NOTE:** There is [another example](../packaged/#external-ssltls-termination) for external SSL/TLS termination for **packaged** installations
#### 1) Virtual host root
@@ -229,10 +241,9 @@ The default scenario is to have OpenProject serve the whole virtual host.
This requires no further configuration for the docker container beyond what is
described above.
Assuming the desired *server name* is `openproject.example.com` the configuration
will look like this:
Let's assume we want OpenProject to be accessed under https://openproject.example.com.
> **NOTE:** There is [another example](../packaged/#external-ssltls-termination) for external SSL/TLS termination for **packaged** installations
The **apache** configuration for this looks as follows.
```
<VirtualHost *:80>
@@ -265,6 +276,37 @@ will look like this:
</VirtualHost>
```
The **nginx** counterpart can be seen below.
```
server {
listen 80;
server_name openproject.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name openproject.example.com;
ssl_certificate /etc/ssl/crt/server.crt;
ssl_certificate_key /etc/ssl/crt/server.key;
proxy_redirect off;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_pass http://127.0.0.1:8080;
}
}
```
#### 2) Location (subdirectory)
Let's assume you want OpenProject to run on your host with the *server name* `example.com`
@@ -277,7 +319,7 @@ need to configure OpenProject accordingly by adding the following options to the
-e OPENPROJECT_RAILS__RELATIVE__URL__ROOT=/openproject
```
The apache configuration for this configuration then looks like this:
The **apache** configuration for this configuration then looks like this:
```
<VirtualHost *:80>
@@ -310,6 +352,39 @@ The apache configuration for this configuration then looks like this:
</VirtualHost>
```
The equivalent **nginx** configuration looks as follows.
```
server {
listen 80;
server_name example.com;
location /openproject {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/ssl/crt/server.crt;
ssl_certificate_key /etc/ssl/crt/server.key;
proxy_redirect off;
location /openproject {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_pass http://127.0.0.1:8080/openproject;
}
}
```
### OpenProject plugins
The docker image itself does not support plugins. But you can create your own docker image to include plugins.
@@ -0,0 +1,84 @@
---
sidebar_navigation:
title: Helm Chart
priority: 280
---
# Helm Chart
## Basic commands
```bash
helm repo add openproject https://charts.openproject.org
helm upgrade --install my-openproject openproject/openproject
```
## Introduction
This chart bootstraps an OpenProject instance, optionally with a PostgreSQL database and Memcached.
## Prerequisites
- Kubernetes 1.16+
- Helm 3.0.0+
- PV provisioner support in the underlying infrastructure
## Installing the Chart
You can install the chart with the release name `my-openproject` in its own namespace like this:
```bash
helm upgrade --create-namespace --namespace openproject --install my-openproject openproject/openproject
```
The namespace is optional, but using it does make it easier to manage the resources
created for OpenProject.
## Updating the configuration
The OpenProject configuration can be changed through environment variables.
You can use `helm upgrade` to set individual values.
For instance:
```
helm upgrade --reuse-values --namespace openproject my-openproject --set environment.OPENPROJECT_IMPRESSUM__LINK=https://www.openproject.org/legal/imprint/ --set environment.OPENPROJECT_APP__TITLE='My OpenProject'
```
Find out more about the [configuration](../../configuration/environment/) section.
## Uninstalling the Chart
To uninstall the release with the name my-openproject do the following:
```bash
helm uninstall --namespace openproject my-openproject
```
> **Note**: This will not remove the persistent volumes created while installing.
> The easiest way to ensure all PVCs are deleted as well is to delete the openproject namespace
> (`kubectl delete namespace openproject`). If you installed OpenProject into the default
> namespace, you can delete the volumes manually one by one.
## Troubleshooting
### Web deployment stuck in `CrashLoopBackoff`
Describing the pod may yield an error like the following:
```
65s) kubelet Error: failed to start container "openproject": Error response from daemon: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: error during container init: error setting cgroup config for procHooks process: failed to write "400000": write /sys/fs/cgroup/cpu,cpuacct/kubepods/burstable/pod990fa25e-dbf0-4fb7-9b31-9d7106473813/openproject/cpu.cfs_quota_us: invalid argument: unknown
```
This can happen when using **minikube**. By default, it initialises the cluster with 2 CPUs only.
Either increase the cluster's resources to have at least 4 CPUs or install the OpenProject helm chart with a reduced CPU limit by adding the following option to the install command:
```
--set resources.limits.cpu=2
```
### Root access in OpenShift
The OpenProject container performs tasks as root during setup.
In [OpenShift](https://www.redhat.com/en/technologies/cloud-computing/openshift) this is not allowed. You will have to [add](https://examples.openshift.pub/deploy/scc-anyuid/) the `anyuid` SCC (Security Context Constraint)
to OpenProject's service account.
@@ -1,5 +1,7 @@
---
sidebar_navigation: false
sidebar_navigation:
title: Kubernetes
priority: 290
---
# Kubernetes
@@ -8,7 +10,3 @@ Kubernetes is a container orchestration tool. As such it can use the
OpenProject docker container in the same manner as shown in the [docker section](../docker/#one-container-per-process-recommended).
In the [openproject-deploy](https://github.com/opf/openproject-deploy/blob/stable/12/kubernetes/README.md) repository we provide further information and an exemplary set of YAML files defining a complete OpenProject setup on Kubernetes.
## Helm
You can find instructions for the official Helm charts under https://charts.openproject.org.
@@ -145,6 +145,23 @@ OpenProject OIDC integration supports [back-channel logouts](https://openid.net/
On the identity provider side, you need to set `https://<OpenProject host>/auth/<provider>/backchannel-logout`. `<provider>` is the identifier of the OIDC configuration as provided above.
#### Respecting self-registration
You can configure OpenProject to restrict which users can register on the system with the [authentication self-registration setting](../authentication-settings)
By default, users returning from a SAML idP will be automatically created. If you'd like for the SAML integration to respect the configured self-registration option, please use setting `limit_self_registration`:
```ruby
options = {
# ... other options
limit_self_registration: true
}
```
### Claims
You can also request [claims](https://openid.net/specs/openid-connect-core-1_0-final.html#Claims) for both the id_token and userinfo endpoint.
+1 -1
View File
@@ -66,7 +66,7 @@ It is now possible to choose between a full 1-week view, a 2-week view or only t
## OpenProject Helm Charts
Starting with OpenProject 12.4 offical [OpenProject Helm charts](../../installation-and-operations/installation/kubernetes/#helm) are availble.
Starting with OpenProject 12.4 offical [OpenProject Helm charts](../../installation-and-operations/installation/helm-chart) are available.
![openproject helm charts](openproject-helm-charts.jpg)
@@ -23,6 +23,8 @@ You can adapt the following under the authentication settings:
c) **Automatic account activation** means that a newly registered user will automatically be active.
**Note:** By default, self-registration is only applied to internal users (logging in with username and password). If you have an identity provider such as LDAP, SAML or OpenID Connect, use the respective settings in their configuration to control which users are applicable for automatic user creation.
3. Define if the **email address should be used as login** name.
4. Define after how many days the **activation email sent to new users will expire**. Afterwards, you will have the possibility to [re-send the activation email](../../users-permissions/users/#resend-user-invitation-via-email) via the user settings.
@@ -29,10 +29,9 @@ You can configure the following options.
2. Optionally enter a **display name**.
3. Enter the **Identifier**.
4. Enter the **Secret**.
5. Press the blue **create** button.
5. Optionally, if you want to honor the system-wide self-registration setting, enable "Limit self registration".
When checked, users will be created according to the [self-registration setting](../authentication-settings).
6. Press the blue **create** button.
## Google Workspace
@@ -233,13 +233,13 @@ Be sure to choose the correct indentation and base key. The items below the `sam
In this section, we detail some of the required and optional configuration options for SAML.
**Mandatory: Response signature verification**
#### 2.1 Mandatory: Response signature verification
SAML responses by identity providers are required to be signed. You can configure this by either specifying the response's certificate fingerprint in `idp_cert_fingerprint` , or by passing the entire PEM-encoded certificate string in `idp_cert` (beware of newlines and formatting the cert, [c.f. the idP certificate options in omniauth-saml](https://github.com/omniauth/omniauth-saml#options))
**Mandatory: Attribute mapping**
#### 2.2 Mandatory: Attribute mapping
Use the key `attribute_statements` to provide mappings for attributes returned by the SAML identity provider's response to OpenProject internal attributes.
@@ -291,7 +291,9 @@ default:
last_name: ['sn']
```
**Optional: Setting the attribute format**
#### 2.3 Optional: Setting the attribute format
By default, the attributes above will be requested with the format `urn:oasis:names:tc:SAML:2.0:attrname-format:basic`.
That means the response should contain attribute names 'mail', etc. as configured above.
@@ -327,7 +329,9 @@ default:
last_name: ['urn:oid:2.5.4.4']
```
**Optional: Request signature and Assertion Encryption**
#### 2.4 Optional: Request signature and Assertion Encryption
Your identity provider may optionally encrypt the assertion response, however note that with the required use of TLS transport security, in many cases this is not necessary. You may wish to use Assertion Encryption if TLS is terminated before the OpenProject application server (e.g., on the load balancer level).
@@ -368,10 +372,26 @@ default:
digest_method: 'http://www.w3.org/2001/04/xmlenc#sha256'
```
With request signing enabled, the certificate will be added to the identity provider to validate the signature of the service provider's request.
#### 2.5. Optional: Restrict who can automatically self-register
You can configure OpenProject to restrict which users can register on the system with the [authentication self-registration setting](../authentication-settings)
By default, users returning from a SAML idP will be automatically created. If you'd like for the SAML integration to respect the configured self-registration option, please use this setting:
```yml
default:
# <-- other configuration -->
mysaml1:
# <-- other configuration -->
limit_self_registration: true
```
### 3: Restarting the server
Once the configuration is completed, restart your OpenProject server with `service openproject restart`. If you configured SAML through settings, this step can be ignored.
@@ -233,4 +233,4 @@ In rare occasions, it is possible for the integration to not be able to fetch al
If OpenProject notifications are not properly displayed in Nextcloud, navigate to *Nextcloud settings → Basic settings → Background jobs* and ensure that _Cron_ is selected.
## ![NC_notifications_not_displayed](Cron_job_settings.png)
![NC_notifications_not_displayed](Cron_job_settings.png)
@@ -34,11 +34,14 @@ export class OpCalendarService extends UntilDestroyedMixin {
}
applyNonWorkingDay({ date }:{ date?:Date }, nonWorkingDays:IDay[]):string[] {
const formatted = moment(date).format('YYYY-MM-DD');
if (date && (this.weekdayService.isNonWorkingDay(date) || nonWorkingDays.find((el) => el.date === formatted))) {
return ['fc-non-working-day'];
if (date) {
// we need to find the UTC date for each date while highlighting non-wrking days on full-calendar
const utcDate = new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds(), date.getUTCMilliseconds());
const formatted = moment(utcDate).format('YYYY-MM-DD');
if (this.weekdayService.isNonWorkingDay(utcDate) || nonWorkingDays.find((el) => el.date === formatted)) {
return ['fc-non-working-day'];
}
}
return [];
}
}
@@ -113,8 +113,8 @@ export class FilterDateTimesValueComponent extends AbstractDateTimeValueControll
const parsed = this
.timezoneService
.parseISODatetime(date)
.utc()
.startOf('day');
.startOf('day')
.utc();
this.begin = this.timezoneService.formattedISODateTime(parsed);
}
@@ -131,8 +131,8 @@ export class FilterDateTimesValueComponent extends AbstractDateTimeValueControll
const parsed = this
.timezoneService
.parseISODatetime(date)
.utc()
.endOf('day');
.endOf('day')
.utc();
this.end = this.timezoneService.formattedISODateTime(parsed);
}
@@ -1,56 +1,74 @@
<div
*ngIf="workPackage && relatedWorkPackage"
class="relation-row relation-row-{{ relatedWorkPackage.id }}"
opFocusWithin=".wp-relations-controls-section button"
opFocusWithin=".relation-row--grid-actions button"
>
<div class="grid-block hierarchy-item">
<div class="grid-content medium-2 collapse">
<div
class="relation-row--grid"
>
<div>
<button
type="button"
class="relation-row--type"
data-qa-selector="op-relation--row-type"
(click)="activateRelationTypeEdit()"
*ngIf="!userInputs.showRelationTypesForm"
>
<span *ngIf="groupByWorkPackageType"
[textContent]="normalizedRelationType"></span>
<span *ngIf="!groupByWorkPackageType"
[ngClass]="highlightingClassForWpType()"
[textContent]="relatedWorkPackage.type.name"></span>
<span
*ngIf="groupByWorkPackageType"
[textContent]="normalizedRelationType"
></span>
<span
*ngIf="!groupByWorkPackageType"
[ngClass]="highlightingClassForWpType()"
[textContent]="relatedWorkPackage.type.name"
></span>
<span class="hidden-for-sighted" [textContent]="text.updateRelation"></span>
</button>
<div class="inline-edit--container inplace-edit"
*ngIf="userInputs.showRelationTypesForm">
<select class="inline-edit--field form--select"
[(ngModel)]="selectedRelationType"
(change)="saveRelationType()"
role="listbox"
opAutofocus
(keydown.escape)="cancelRelationTypeEditOnEscape($event)">
<option *ngFor="let relationType of availableRelationTypes"
[textContent]="relationType.label"
[ngValue]="relationType"></option>
<div
class="inline-edit--container inplace-edit"
*ngIf="userInputs.showRelationTypesForm"
>
<select
class="inline-edit--field form--select"
[(ngModel)]="selectedRelationType"
(change)="saveRelationType()"
role="listbox"
opAutofocus
(keydown.escape)="cancelRelationTypeEditOnEscape($event)"
>
<option
*ngFor="let relationType of availableRelationTypes"
[textContent]="relationType.label"
[ngValue]="relationType"
></option>
</select>
</div>
</div>
<div class="grid-content medium-5 collapse"
*ngIf="relatedWorkPackage">
<a uiSref="work-packages.show.tabs"
[uiParams]="{ workPackageId: relatedWorkPackage.id, tabIdentifier: 'relations' }"
class="wp-relations--subject-field"
[textContent]="relatedWorkPackage.subjectWithId(0)"
[attr.aria-label]="normalizedRelationType + ' ' + relatedWorkPackage.subjectWithId(0)">
</a>
</div>
<a
class="relation-row--grid-id"
data-qa-selector="op-relation--row-id"
uiSref="work-packages.show.tabs"
[uiParams]="{ workPackageId: relatedWorkPackage.id, tabIdentifier: 'relations' }"
>#{{relatedWorkPackage.id}}</a>
<div class="grid-content medium-3 collapse wp-relations-status-field">
<edit-form *ngIf="relatedWorkPackage" [resource]="relatedWorkPackage">
<op-editable-attribute-field [resource]="relatedWorkPackage" fieldName="status"></op-editable-attribute-field>
</edit-form>
</div>
<a
class="relation-row--grid-subject"
data-qa-selector="op-relation--row-subject"
uiSref="work-packages.show.tabs"
[uiParams]="{ workPackageId: relatedWorkPackage.id, tabIdentifier: 'relations' }"
[textContent]="relatedWorkPackage.subject"
[attr.aria-label]="normalizedRelationType + ' ' + relatedWorkPackage.subjectWithId(0)"
></a>
<div class="grid-content medium-2 collapse wp-relations-controls-section"
ng-class="{'-expanded': userInputs.showRelationInfo }">
<edit-form *ngIf="relatedWorkPackage" [resource]="relatedWorkPackage">
<op-editable-attribute-field [resource]="relatedWorkPackage" fieldName="status"></op-editable-attribute-field>
</edit-form>
<div
class="relation-row--grid-actions"
>
<button
type="button"
class="spot-link wp-relations--description-btn"
@@ -58,8 +76,10 @@
[title]="text.description_label"
(click)="userInputs.showRelationInfo = !userInputs.showRelationInfo"
>
<op-icon icon-classes="icon-info1 icon-no-color -padded wp-relations--icon wp-relations--description-icon"
[icon-title]="text.toggleDescription"></op-icon>
<op-icon
icon-classes="icon-info1 icon-no-color -padded wp-relations--icon wp-relations--description-icon"
[icon-title]="text.toggleDescription"
></op-icon>
</button>
<button
*ngIf="!!relation.delete"
@@ -69,14 +89,18 @@
[title]="text.removeButton"
(click)="removeRelation()"
>
<op-icon icon-classes="icon-remove icon-no-color -padded wp-relations--icon"
[icon-title]="text.removeButton"></op-icon>
<op-icon
icon-classes="icon-remove icon-no-color -padded wp-relations--icon"
[icon-title]="text.removeButton"
></op-icon>
</button>
</div>
</div>
<div class="grid-block hierarchy-item description-container"
*ngIf="userInputs.showRelationInfo">
<div
*ngIf="userInputs.showRelationInfo"
class="grid-block hierarchy-item description-container"
>
<button
*ngIf="!userInputs.showDescriptionEditForm"
type="button"
@@ -84,23 +108,26 @@
[class.-placeholder]="!relation.description"
(click)="startDescriptionEdit()"
[textContent]="relation.description || text.placeholder.description"
></button>
<div
class="wp-relation--description-wrapper textarea-wrapper"
*ngIf="userInputs.showDescriptionEditForm"
>
</button>
<div class="wp-relation--description-wrapper textarea-wrapper"
*ngIf="userInputs.showDescriptionEditForm">
<textarea
#relationDescriptionTextarea
autofocus
class="wp-relation--description-textarea"
name="description"
(keyup)="handleDescriptionKey($event)"
[(ngModel)]="userInputs.newRelationText"></textarea>
<edit-field-controls [fieldController]="fieldController"
(onSave)="saveDescription()"
(onCancel)="cancelDescriptionEdit()"
[saveTitle]="text.save"
[cancelTitle]="text.cancel">
</edit-field-controls>
<textarea
#relationDescriptionTextarea
autofocus
class="wp-relation--description-textarea"
name="description"
(keyup)="handleDescriptionKey($event)"
[(ngModel)]="userInputs.newRelationText"
></textarea>
<edit-field-controls
[fieldController]="fieldController"
(onSave)="saveDescription()"
(onCancel)="cancelDescriptionEdit()"
[saveTitle]="text.save"
[cancelTitle]="text.cancel"
></edit-field-controls>
</div>
</div>
</div>
@@ -22,7 +22,7 @@
</div>
<div
class="content"
class="relation-container"
*ngIf="relatedWorkPackages"
>
<wp-relation-row
@@ -44,8 +44,8 @@ import {
NG_VALUE_ACCESSOR,
} from '@angular/forms';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';
import { merge, Observable, of } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
import { ID } from '@datorama/akita';
import { HalResourceService } from 'core-app/features/hal/services/hal-resource.service';
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
@@ -173,7 +173,7 @@ export class ProjectAutocompleterComponent implements ControlValueAccessor {
populateInputsFromDataset(this);
}
matchingItems(elements:IProjectAutocompleteItem[], matching:string):Observable<IProjectAutocompleteItem[]> {
private matchingItems(elements:IProjectAutocompleteItem[], matching:string):Observable<IProjectAutocompleteItem[]> {
let filtered:IProjectAutocompleteItem[];
if (matching === '' || !matching) {
@@ -186,6 +186,25 @@ export class ProjectAutocompleterComponent implements ControlValueAccessor {
return of(filtered);
}
private disableSelectedItems(
projects:IProjectAutocompleteItem[],
value:IProjectAutocompleterData|IProjectAutocompleterData[]|null,
) {
if (!this.multiple) {
return projects;
}
const normalizedValue = (value || []);
const arrayedValue = (Array.isArray(normalizedValue) ? normalizedValue : [normalizedValue]).map((p) => p.href || p.id);
return projects.map((project) => {
const isSelected = !!arrayedValue.find((selected) => selected === this.projectTracker(project));
return {
...project,
disabled: isSelected || project.disabled,
};
});
}
public getAvailableProjects(searchTerm:string):Observable<IProjectAutocompleteItem[]> {
if (this.dataLoaded === true) {
return this.matchingItems(this.projects, searchTerm).pipe(
@@ -194,6 +213,11 @@ export class ProjectAutocompleterComponent implements ControlValueAccessor {
map((projects) => buildTree(projects)),
map((projects) => recursiveSort(projects)),
map((projectTreeItems) => flattenProjectTree(projectTreeItems)),
switchMap(
(projects) => merge(of([]), this.valueChange).pipe(
map(() => this.disableSelectedItems(projects, this.value)),
),
),
);
}
return getPaginatedResults<IProject>(
@@ -234,10 +258,19 @@ export class ProjectAutocompleterComponent implements ControlValueAccessor {
children: [],
}))),
map(this.mapResultsFn),
map((projects) => { this.dataLoaded = true; this.projects = projects; return projects.sort((a, b) => a.ancestors.length - b.ancestors.length); }),
map((projects) => {
this.dataLoaded = true;
this.projects = projects;
return projects.sort((a, b) => a.ancestors.length - b.ancestors.length);
}),
map((projects) => buildTree(projects)),
map((projects) => recursiveSort(projects)),
map((projectTreeItems) => flattenProjectTree(projectTreeItems)),
switchMap(
(projects) => merge(of([]), this.valueChange).pipe(
map(() => this.disableSelectedItems(projects, this.value)),
),
),
);
}
@@ -270,8 +270,11 @@ export class OpModalSingleDatePickerComponent implements ControlValueAccessor, O
}
writeWorkingValue(value:string):void {
const date = new Date(value);
// since new Date() returns a date in our local timezone, we need to find the UTC date
const utcDate = new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds(), date.getUTCMilliseconds());
this.workingValue = value;
this.workingDate = new Date(value);
this.workingDate = utcDate;
}
writeValue(value:string):void {
@@ -140,6 +140,9 @@ $datepicker--selected-border-radius: 5px
border-radius: 0
box-shadow: none !important
&:hover
border-color: $spot-color-basic-gray-3
&.flatpickr-non-working-day
@include non-working-day
@@ -152,8 +155,8 @@ $datepicker--selected-border-radius: 5px
border-radius: 0
&:hover
color: $spot-color-basic-gray-1 !important
border-color: #e6e6e6 !important
color: $spot-color-basic-gray-1
border-color: $spot-color-basic-gray-3
&.selected:not(.startRange, .endRange)
border-radius: $datepicker--selected-border-radius
@@ -165,6 +168,10 @@ $datepicker--selected-border-radius: 5px
border-color: var(--primary-color)
color: $spot-color-basic-white
&:hover
background: var(--primary-color-dark)
border-color: var(--primary-color-dark)
&.startRange
border-radius: $datepicker--selected-border-radius 0 0 $datepicker--selected-border-radius
@@ -180,6 +187,10 @@ $datepicker--selected-border-radius: 5px
border-color: var(--primary-color--minor3)
border-radius: 0
&:hover
color: var(--primary-color-dark)
border-color: var(--primary-color-dark)
&.flatpickr-non-working-day
background: $spot-color-basic-gray-6
border-color: $spot-color-basic-gray-6
@@ -28,12 +28,16 @@
.detail-panel-description-content
.relation
clear: both //resolve the problem if inside are elements with float
//resolve the problem if inside are elements with float
clear: both
h3
cursor: pointer
a
text-decoration: none
color: inherit
i
font-size: 0.8rem
@@ -43,31 +47,48 @@
.hierarchy-item
margin-bottom: 2px
.relation-container
margin-bottom: $spot-spacing-1
.relation-row
line-height: 2em
.attribute-header
font-size: 0.8em
text-transform: uppercase
font-weight: bold
.description-section
border: 1px dotted lightblue
padding: 4px
.inline-edit--container
@include text-shortener
// Similar to inner span's line-height
line-height: 1.6em
.inline-edit--display-field
vertical-align: middle
.controls-container
text-align: right
&--grid
display: grid
align-items: center
grid-template: "id info subject status actions" / 10% 12.5% 45% 22.5% 10%
margin-bottom: $spot-spacing-0_5
@media #{$spot-mq-mobile}
grid-template: "id info status actions" "subject subject subject subject" / 25% 30% 30% 15%
&-actions
grid-area: actions
display: flex
justify-content: end
&-id
@include text-shortener()
grid-area: id
font-size: 0.875rem
&-subject
@include text-shortener()
grid-area: subject
font-size: 0.875rem
.wp-relations-hierarchy-section
margin-top: 35px
.wp-relations-hierarchy-subject
@include text-shortener
display: block
.wp-relations-controls-section
text-align: right
flex-shrink: 1
@@ -85,6 +106,7 @@
.wp-relations-create-button
margin: 0.25rem 0
line-height: 1.5
.-create-button-full-width
margin-top: 1.5em
width: 100%
+2 -2
View File
@@ -145,8 +145,8 @@ namespace :redmine do
username: ENV.fetch('username', nil),
password: ENV.fetch('password', nil),
folder: ENV.fetch('folder', nil),
move_on_success: ActiveRecord::Type::Boolean.new.cast(ENV.fetch('move_on_success', nil)),
move_on_failure: ActiveRecord::Type::Boolean.new.cast(ENV.fetch('move_on_failure', nil))
move_on_success: ENV.fetch('move_on_success', nil),
move_on_failure: ENV.fetch('move_on_failure', nil)
}
Redmine::IMAP.check(imap_options, options_from_env)
@@ -38,6 +38,8 @@ module OpenProject::AuthPlugins
author_url: 'https://www.openproject.org',
bundled: true
patch_with_namespace :Users, :RegisterUserService
config.to_prepare do
OpenProject::AuthPlugins::Hooks
end
@@ -0,0 +1,30 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2023 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module OpenProject::AuthPlugins::Patches
end
@@ -0,0 +1,45 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2023 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module OpenProject::AuthPlugins::Patches::RegisterUserServicePatch
def self.included(base) # :nodoc:
base.prepend InstanceMethods
end
module InstanceMethods
def skip_omniauth_user?
super || limit_self_registration?(user)
end
def limit_self_registration?(user)
provider = user.authentication_provider.downcase
OpenProject::Plugins::AuthPlugin.limit_self_registration? provider:
end
end
end
@@ -92,6 +92,15 @@ module OpenProject::Plugins
[camelization, name].compact.first.underscore.to_sym
end
##
# Indicates whether or not self registration should be limited for the provider
# with the given name.
#
# @param provider [String] Name of the provider
def self.limit_self_registration?(provider:)
Hash(find_provider_by_name(provider))[:limit_self_registration]
end
def self.warn_unavailable(name)
RequestStore.fetch("warn_unavailable_auth_#{name}") do
Rails.logger.warn { "OmniAuth SSO strategy #{name} is only available for Enterprise Editions." }
@@ -62,11 +62,11 @@ module OpenIDConnect
end
def create_params
params.require(:openid_connect_provider).permit(:name, :display_name, :identifier, :secret)
params.require(:openid_connect_provider).permit(:name, :display_name, :identifier, :secret, :limit_self_registration)
end
def update_params
params.require(:openid_connect_provider).permit(:display_name, :identifier, :secret)
params.require(:openid_connect_provider).permit(:display_name, :identifier, :secret, :limit_self_registration)
end
def find_provider
@@ -21,14 +21,27 @@ module OpenIDConnect
delegate :scope, to: :omniauth_provider, allow_nil: true
delegate :to_h, to: :omniauth_provider, allow_nil: false
##
# Controls whether or not self registration shall be limited for this provider.
#
# See also:
# - OpenProject::Plugins::AuthPlugin.limit_self_registration?
# - OpenProject::AuthPlugins::Patches::RegisterUserServicePatch
attr_reader :limit_self_registration
def initialize(omniauth_provider)
@omniauth_provider = omniauth_provider
@errors = ActiveModel::Errors.new(self)
@display_name = omniauth_provider.to_h[:display_name]
@limit_self_registration = initial_value_for_limit_self_registration
end
def self.initialize_with(params)
new(NewProvider.new(params))
do_limit = params[:limit_self_registration]
new(NewProvider.new(params.except(:limit_self_registration))).tap do |p|
p.limit_self_registration = String(do_limit).to_bool unless do_limit.nil?
end
end
def new_record?
@@ -39,6 +52,24 @@ module OpenIDConnect
omniauth_provider.is_a?(OmniAuth::OpenIDConnect::Provider)
end
def limit_self_registration?
@limit_self_registration
end
def limit_self_registration=(value)
@limit_self_registration = value
end
def to_h
return {} if omniauth_provider.nil?
omniauth_provider.to_h.merge(limit_self_registration: limit_self_registration?)
end
def limit_self_registration_default
name == "google" # limit by default only for Google since anyone can sign in
end
def id
return nil unless persisted?
@@ -46,33 +77,64 @@ module OpenIDConnect
end
def valid?
@errors.add(:name, :invalid) unless ALLOWED_TYPES.include?(name)
@errors.add(:name, :invalid) unless type_allowed?(name)
@errors.add(:identifier, :blank) if identifier.blank?
@errors.add(:secret, :blank) if secret.blank?
@errors.none?
end
##
# Checks if the provider with the given name is of an allowed type.
#
# Types can be followed by a period and arbitrary names to add several
# providers of the same type. E.g. 'azure', 'azure.dep1', 'azure.dep2'.
def type_allowed?(name)
ALLOWED_TYPES.any? { |allowed| name =~ /\A#{allowed}(\..+)?\Z/ }
end
def save
return false unless valid?
config = Setting.plugin_openproject_openid_connect || Hash.new
config["providers"] ||= Hash.new
config["providers"][name] = omniauth_provider.to_h.stringify_keys
Setting.plugin_openproject_openid_connect = config
Setting.plugin_openproject_openid_connect = setting_with_provider
true
end
def destroy
config = Setting.plugin_openproject_openid_connect
config["providers"] ||= {}
config["providers"].delete(name)
Setting.plugin_openproject_openid_connect = config
Setting.plugin_openproject_openid_connect = setting_without_provider
true
end
def setting_with_provider
setting.deep_merge "providers" => { name => to_h.stringify_keys }
end
def setting_without_provider
setting.tap do |s|
s["providers"].delete name
end
end
def setting
Hash(Setting.plugin_openproject_openid_connect).tap do |h|
h["providers"] ||= Hash.new
end
end
# https://api.rubyonrails.org/classes/ActiveModel/Errors.html
def read_attribute_for_validation(attr)
send(attr)
end
private
def initial_value_for_limit_self_registration
if omniauth_provider.configuration&.has_key? :limit_self_registration
omniauth_provider.configuration[:limit_self_registration]
else
limit_self_registration_default
end
end
end
end
@@ -22,4 +22,11 @@
<div class="form--field -required">
<%= f.text_field :secret, required: true, container_class: '-middle' %>
</div>
<div class="form--field">
<%= f.check_box :limit_self_registration, required: false, container_class: '-middle' %>
<div class="form--field-instructions">
<%= I18n.t('openid_connect.setting_instructions.limit_self_registration') %>
</div>
</div>
</fieldset>
@@ -9,6 +9,7 @@ af:
identifier: Identifiseerder
secret: Secret
scope: Scope
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID providers
providers:
@@ -17,3 +18,6 @@ af:
no_results_table: No providers have been defined yet.
plural: OpenID providers
singular: OpenID provider
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ ar:
identifier: المعرّف
secret: Secret
scope: Scope
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID providers
providers:
@@ -17,3 +18,6 @@ ar:
no_results_table: No providers have been defined yet.
plural: OpenID providers
singular: OpenID provider
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ az:
identifier: Identifier
secret: Secret
scope: Scope
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID providers
providers:
@@ -17,3 +18,6 @@ az:
no_results_table: No providers have been defined yet.
plural: OpenID providers
singular: OpenID provider
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ be:
identifier: Identifier
secret: Secret
scope: Scope
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID providers
providers:
@@ -17,3 +18,6 @@ be:
no_results_table: No providers have been defined yet.
plural: OpenID providers
singular: OpenID provider
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ bg:
identifier: Идентификатор
secret: Secret
scope: Scope
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID providers
providers:
@@ -17,3 +18,6 @@ bg:
no_results_table: No providers have been defined yet.
plural: OpenID providers
singular: OpenID provider
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ ca:
identifier: Identificador
secret: Secret
scope: Abast
limit_self_registration: Limit self registration
openid_connect:
menu_title: Proveïdor dOpenID
providers:
@@ -17,3 +18,6 @@ ca:
no_results_table: Encara no s'han definit cap proveïdor.
plural: Proveïdors dOpenID
singular: Proveïdor dOpenID
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ ckb-IR:
identifier: Identifier
secret: Secret
scope: Scope
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID providers
providers:
@@ -17,3 +18,6 @@ ckb-IR:
no_results_table: No providers have been defined yet.
plural: OpenID providers
singular: OpenID provider
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ cs:
identifier: Identifikátor
secret: Secret
scope: Rozsah
limit_self_registration: Limit self registration
openid_connect:
menu_title: Poskytovatelé OpenID
providers:
@@ -17,3 +18,6 @@ cs:
no_results_table: Zatím nebyli definováni žádní poskytovatelé.
plural: Poskytovatelé OpenID
singular: Poskytovatel OpenID
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ da:
identifier: ID
secret: Secret
scope: Scope
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID providers
providers:
@@ -17,3 +18,6 @@ da:
no_results_table: No providers have been defined yet.
plural: OpenID providers
singular: OpenID provider
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ de:
identifier: Kennung
secret: Secret
scope: Geltungsbereich
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID-Anbieter
providers:
@@ -17,3 +18,6 @@ de:
no_results_table: Noch keine Anbieter definiert.
plural: OpenID-Anbieter
singular: OpenID-Anbieter
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ el:
identifier: Αναγνωριστικό
secret: Μυστικό
scope: Φυσικό Αντικείμενο
limit_self_registration: Limit self registration
openid_connect:
menu_title: Πάροχοι OpenID
providers:
@@ -17,3 +18,6 @@ el:
no_results_table: Δεν έχουν οριστεί πάροχοι ακόμη.
plural: Πάροχοι OpenID
singular: Πάροχος OpenID
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ eo:
identifier: Identigilo
secret: Secret
scope: Scope
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID providers
providers:
@@ -17,3 +18,6 @@ eo:
no_results_table: No providers have been defined yet.
plural: OpenID providers
singular: OpenID provider
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ es:
identifier: Identificador
secret: Secreto
scope: Ámbito
limit_self_registration: Limit self registration
openid_connect:
menu_title: Proveedores de OpenID
providers:
@@ -17,3 +18,6 @@ es:
no_results_table: Aún no se han definido proveedores.
plural: Proveedores de OpenID
singular: Proveedor de OpenID
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ et:
identifier: Identifikaator
secret: Secret
scope: Scope
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID providers
providers:
@@ -17,3 +18,6 @@ et:
no_results_table: No providers have been defined yet.
plural: OpenID providers
singular: OpenID provider
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ eu:
identifier: Identifier
secret: Secret
scope: Scope
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID providers
providers:
@@ -17,3 +18,6 @@ eu:
no_results_table: No providers have been defined yet.
plural: OpenID providers
singular: OpenID provider
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ fa:
identifier: شناسه
secret: Secret
scope: Scope
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID providers
providers:
@@ -17,3 +18,6 @@ fa:
no_results_table: No providers have been defined yet.
plural: OpenID providers
singular: OpenID provider
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ fi:
identifier: Tunniste
secret: Secret
scope: Scope
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID providers
providers:
@@ -17,3 +18,6 @@ fi:
no_results_table: No providers have been defined yet.
plural: OpenID providers
singular: OpenID provider
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ fil:
identifier: Ang pagkakakilanlan
secret: Secret
scope: Scope
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID providers
providers:
@@ -17,3 +18,6 @@ fil:
no_results_table: No providers have been defined yet.
plural: OpenID providers
singular: OpenID provider
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ fr:
identifier: Identifiant
secret: Secret
scope: Portée
limit_self_registration: Limit self registration
openid_connect:
menu_title: Fournisseurs OpenID
providers:
@@ -17,3 +18,6 @@ fr:
no_results_table: Aucun fournisseur n'a encore été défini.
plural: Fournisseurs OpenID
singular: Fournisseur OpenID
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ he:
identifier: מזהה
secret: Secret
scope: Scope
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID providers
providers:
@@ -17,3 +18,6 @@ he:
no_results_table: No providers have been defined yet.
plural: OpenID providers
singular: OpenID provider
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ hi:
identifier: पहचानकर्ता
secret: Secret
scope: Scope
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID providers
providers:
@@ -17,3 +18,6 @@ hi:
no_results_table: No providers have been defined yet.
plural: OpenID providers
singular: OpenID provider
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ hr:
identifier: Identifikator
secret: Secret
scope: Scope
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID providers
providers:
@@ -17,3 +18,6 @@ hr:
no_results_table: No providers have been defined yet.
plural: OpenID providers
singular: OpenID provider
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ hu:
identifier: Azonosító
secret: Titok
scope: Hatókör
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID szolgáltató
providers:
@@ -17,3 +18,6 @@ hu:
no_results_table: Nincs szolgáltató definiálva
plural: OpenID szolgáltatók
singular: OpenID szolgáltató
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ id:
identifier: Pengenal
secret: Secret
scope: Scope
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID providers
providers:
@@ -17,3 +18,6 @@ id:
no_results_table: Belum ada penyedia yang ditentukan.
plural: penyedia OpenID
singular: penyedia OpenID
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ it:
identifier: Identificativo
secret: Parola chiave
scope: Ambito
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID provider
providers:
@@ -17,3 +18,6 @@ it:
no_results_table: Non è stato definito alcun provider.
plural: OpenID provider
singular: Provider OpenID
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ ja:
identifier: 識別子
secret: シークレット
scope: スコープ
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID プロバイダー
providers:
@@ -17,3 +18,6 @@ ja:
no_results_table: プロバイダーが定義されていません。
plural: OpenID プロバイダー
singular: OpenID プロバイダー
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ ko:
identifier: 식별자
secret: 비밀번호
scope: 범위
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID 공급자
providers:
@@ -17,3 +18,6 @@ ko:
no_results_table: 아직 정의된 공급자가 없습니다.
plural: OpenID 공급자
singular: OpenID 공급자
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ lt:
identifier: Identifikatorius
secret: Paslaptis
scope: Apimtis
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID tiekėjai
providers:
@@ -17,3 +18,6 @@ lt:
no_results_table: Dar neapibrėžtas joks tiekėjas.
plural: OpenID tiekėjai
singular: OpenID tiekėjas
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ lv:
identifier: Identifikators
secret: Secret
scope: Scope
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID providers
providers:
@@ -17,3 +18,6 @@ lv:
no_results_table: No providers have been defined yet.
plural: OpenID providers
singular: OpenID provider
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ mn:
identifier: Identifier
secret: Secret
scope: Scope
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID providers
providers:
@@ -17,3 +18,6 @@ mn:
no_results_table: No providers have been defined yet.
plural: OpenID providers
singular: OpenID provider
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ ne:
identifier: परिचायक
secret: Secret
scope: Scope
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID providers
providers:
@@ -17,3 +18,6 @@ ne:
no_results_table: No providers have been defined yet.
plural: OpenID providers
singular: OpenID provider
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ nl:
identifier: Identifier
secret: Geheim
scope: Scope
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID aanbieders
providers:
@@ -17,3 +18,6 @@ nl:
no_results_table: Er zijn nog geen aanbieders gedefinieerd.
plural: OpenID aanbieders
singular: OpenID aanbieders
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@
identifier: Identifikator
secret: Hemmelig
scope: Omfang
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID leverandører
providers:
@@ -17,3 +18,6 @@
no_results_table: Ingen leverandører har blitt definert ennå.
plural: OpenID leverandører
singular: OpenID-leverandør
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ pl:
identifier: Identyfikator
secret: Tajny klucz
scope: Zakres
limit_self_registration: Limit self registration
openid_connect:
menu_title: Dostawcy OpenID
providers:
@@ -17,3 +18,6 @@ pl:
no_results_table: Jeszcze nie określono żadnych dostawców.
plural: Dostawcy OpenID
singular: Dostawca OpenID
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ pt:
identifier: Identificador
secret: Chave
scope: Escopo
limit_self_registration: Limit self registration
openid_connect:
menu_title: Provedores OpenID
providers:
@@ -17,3 +18,6 @@ pt:
no_results_table: Nenhum provedor foi definido.
plural: Provedores OpenID
singular: Provedor OpenID
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ ro:
identifier: Identificator
secret: Secret
scope: Scop
limit_self_registration: Limit self registration
openid_connect:
menu_title: Furnizori OpenID
providers:
@@ -17,3 +18,6 @@ ro:
no_results_table: Încă nu au fost definiți furnizori.
plural: Furnizori OpenID
singular: Furnizor de autentificare
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ ru:
identifier: Идентификатор
secret: Секретный ключ
scope: Область
limit_self_registration: Limit self registration
openid_connect:
menu_title: Провайдеры OpenID
providers:
@@ -17,3 +18,6 @@ ru:
no_results_table: Ни один провайдер еще не был определен.
plural: Провайдеры OpenID
singular: Провайдер OpenID
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ rw:
identifier: Identifier
secret: Secret
scope: Scope
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID providers
providers:
@@ -17,3 +18,6 @@ rw:
no_results_table: No providers have been defined yet.
plural: OpenID providers
singular: OpenID provider
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ si:
identifier: හඳුනාගැනීමේ
secret: Secret
scope: Scope
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID providers
providers:
@@ -17,3 +18,6 @@ si:
no_results_table: No providers have been defined yet.
plural: OpenID providers
singular: OpenID provider
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ sk:
identifier: Identifikátor
secret: Secret
scope: Scope
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID providers
providers:
@@ -17,3 +18,6 @@ sk:
no_results_table: No providers have been defined yet.
plural: OpenID providers
singular: OpenID provider
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ sl:
identifier: Identifikator
secret: Skrivnost
scope: Področje
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID ponudniki
providers:
@@ -17,3 +18,6 @@ sl:
no_results_table: Zaenkrat še ni opredeljenih ponudnikov.
plural: OpenID ponudniki
singular: OpenID ponudnik
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ sr:
identifier: Identifier
secret: Secret
scope: Scope
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID providers
providers:
@@ -17,3 +18,6 @@ sr:
no_results_table: No providers have been defined yet.
plural: OpenID providers
singular: OpenID provider
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ sv:
identifier: Identifierare
secret: Hemlighet
scope: Omfattning
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID leverantörer
providers:
@@ -17,3 +18,6 @@ sv:
no_results_table: Inga leverantörer har definierats ännu.
plural: OpenID leverantörer
singular: OpenID leverantör
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ th:
identifier: รหัส
secret: Secret
scope: Scope
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID providers
providers:
@@ -17,3 +18,6 @@ th:
no_results_table: No providers have been defined yet.
plural: OpenID providers
singular: OpenID provider
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ tr:
identifier: Tanımlayıcı
secret: Gizli
scope: kapsam
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID sağlayıcıları
providers:
@@ -17,3 +18,6 @@ tr:
no_results_table: Henüz bir sağlayıcı tanımlanmadı.
plural: OpenID sağlayıcıları
singular: OpenID sağlayıcı
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ uk:
identifier: Ідентифікатор
secret: Таємний код
scope: Область використання
limit_self_registration: Limit self registration
openid_connect:
menu_title: Постачальники OpenID
providers:
@@ -17,3 +18,6 @@ uk:
no_results_table: Ще не додано жодного постачальника.
plural: Постачальники OpenID
singular: OpenID постачальник
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ vi:
identifier: Định danh
secret: Secret
scope: Scope
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID providers
providers:
@@ -17,3 +18,6 @@ vi:
no_results_table: No providers have been defined yet.
plural: OpenID providers
singular: OpenID provider
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -9,6 +9,7 @@ zh-TW:
identifier: 識別碼
secret: 金鑰
scope: 範圍
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID 提供者
providers:
@@ -17,3 +18,6 @@ zh-TW:
no_results_table: 尚未定義任何提供者。
plural: OpenID 提供者
singular: OpenID 提供者
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -10,6 +10,7 @@ de:
identifier: Identifier
secret: Secret
scope: Scope
limit_self_registration: Selbstregistrierung einschränken
openid_connect:
menu_title: OpenID-Provider
providers:
@@ -20,3 +21,7 @@ de:
singular: OpenID-Provider
upsale:
description: Use existing OpenID credentials with OpenProject for easier access and interoperability with a range of other providers.
setting_instructions:
limit_self_registration: >
Wenn diese Option aktiv ist, können sich neue Nutzer mit diesem OpenID-Provider nur registrieren,
wenn die Selbstregistrierungs-Einstellung es erlaubt.
@@ -10,6 +10,7 @@ en:
identifier: Identifier
secret: Secret
scope: Scope
limit_self_registration: Limit self registration
openid_connect:
menu_title: OpenID providers
providers:
@@ -18,3 +19,6 @@ en:
no_results_table: No providers have been defined yet.
plural: OpenID providers
singular: OpenID provider
setting_instructions:
limit_self_registration: >
If enabled users can only register using this provider if the self registration setting allows for it.
@@ -105,11 +105,40 @@ describe OpenIDConnect::ProvidersController do
end
describe '#create' do
it 'is successful if valid params' do
post :create, params: { openid_connect_provider: valid_params }
expect(flash[:notice]).to eq(I18n.t(:notice_successful_create))
expect(Setting.plugin_openproject_openid_connect["providers"]).to have_key("azure")
expect(response).to be_redirect
context 'with valid params' do
let(:params) { { openid_connect_provider: valid_params } }
before do
post :create, params:
end
it 'is successful' do
expect(flash[:notice]).to eq(I18n.t(:notice_successful_create))
expect(Setting.plugin_openproject_openid_connect["providers"]).to have_key("azure")
expect(response).to be_redirect
end
context 'with limit_self_registration checked' do
let(:params) do
{ openid_connect_provider: valid_params.merge(limit_self_registration: 1) }
end
it 'sets the setting' do
expect(OpenProject::Plugins::AuthPlugin)
.to be_limit_self_registration provider: valid_params[:name]
end
end
context 'with limit_self_registration unchecked' do
let(:params) do
{ openid_connect_provider: valid_params.merge(limit_self_registration: 0) }
end
it 'does not set the setting' do
expect(OpenProject::Plugins::AuthPlugin)
.not_to be_limit_self_registration provider: valid_params[:name]
end
end
end
it 'renders an error if invalid params' do
@@ -130,6 +159,39 @@ describe OpenIDConnect::ProvidersController do
expect(assigns[:provider]).to be_present
expect(response).to render_template 'edit'
end
context(
'with limit_self_registration set',
with_settings: {
plugin_openproject_openid_connect: {
"providers" => {
"azure" => {
"identifier" => "IDENTIFIER",
"secret" => "SECRET",
"limit_self_registration" => true
}
}
}
}
) do
before do
get :edit, params: { id: 'azure' }
end
it 'shows limit_self_registration as checked' do
expect(assigns[:provider]).to be_limit_self_registration
end
end
context 'with limit_self_registration not set' do
before do
get :edit, params: { id: 'azure' }
end
it 'shows limit_self_registration as unchecked' do
expect(assigns[:provider]).not_to be_limit_self_registration
end
end
end
context 'when not found' do
@@ -142,11 +204,13 @@ describe OpenIDConnect::ProvidersController do
end
describe '#update' do
context 'when found', with_settings: {
plugin_openproject_openid_connect: {
"providers" => { "azure" => { "identifier" => "IDENTIFIER", "secret" => "SECRET" } }
}
} do
context 'when found' do
before do
Setting.plugin_openproject_openid_connect = {
"providers" => { "azure" => { "identifier" => "IDENTIFIER", "secret" => "SECRET" } }
}
end
it 'successfully updates the provider configuration' do
put :update, params: { id: "azure", openid_connect_provider: valid_params.merge(secret: "NEWSECRET") }
expect(response).to be_redirect
@@ -158,11 +222,13 @@ describe OpenIDConnect::ProvidersController do
end
describe '#destroy' do
context 'when found', with_settings: {
plugin_openproject_openid_connect: {
"providers" => { "azure" => { "identifier" => "IDENTIFIER", "secret" => "SECRET" } }
}
} do
context 'when found' do
before do
Setting.plugin_openproject_openid_connect = {
"providers" => { "azure" => { "identifier" => "IDENTIFIER", "secret" => "SECRET" } }
}
end
it 'removes the provider' do
delete :destroy, params: { id: "azure" }
expect(response).to be_redirect
@@ -0,0 +1,96 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2023 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
require 'spec_helper'
describe OpenIDConnect::Provider do
let(:provider) do
described_class.initialize_with name: "azure", identifier: "id", secret: "secret"
end
def auth_plugin
OpenProject::Plugins::AuthPlugin
end
describe 'limit_self_registration' do
before do
# required so that the auth plugin sees any providers (ee feature)
allow(EnterpriseToken).to receive(:show_banners?).and_return false
end
context 'with no limited providers' do
it "shows the provider as unlimited" do
expect(auth_plugin).not_to be_limit_self_registration provider: provider.name
end
context 'when set to true' do
before do
provider.limit_self_registration = true
end
it "saving the provider makes it limited" do
provider.save
expect(auth_plugin).to be_limit_self_registration provider: provider.name
end
end
context 'when set to false' do
before do
provider.limit_self_registration = false
end
it "saving the provider does nothing" do
provider.save
expect(auth_plugin).not_to be_limit_self_registration provider: provider.name
end
end
end
context(
'with a limited provider',
with_settings: {
plugin_openproject_openid_connect: {
"providers" => {
"azure" => {
"name" => "azure",
"identifier" => "id",
"secret" => "secret",
"limit_self_registration" => true
}
}
}
}
) do
it "shows the provider as limited" do
expect(auth_plugin).to be_limit_self_registration provider: provider.name
end
end
end
end
@@ -72,6 +72,24 @@ describe Projects::ArchiveContract do
include_examples 'contract is valid'
end
context 'when some of subprojects are archived but not all' do
before do
subproject1.update_column(:active, false)
create(:member, user: current_user, project: subproject2, roles: [archivist_role])
end
include_examples 'contract is valid'
end
context 'when all of subprojects are archived' do
before do
subproject1.update_column(:active, false)
subproject2.update_column(:active, false)
end
include_examples 'contract is valid'
end
end
include_examples 'contract is valid for active admins and invalid for regular users'
+4
View File
@@ -189,6 +189,10 @@ describe 'Working Days', js: true do
it 'can add non-working days' do
click_on 'Non-working day'
# Check if a date is correctly highlighted after selecting it in different time zones
datepicker.select_day 5
datepicker.expect_day '5'
# It can cancel and reopen
page.within('[data-qa-selector="op-datepicker-modal"]') do
click_on 'Cancel'
+2 -2
View File
@@ -128,7 +128,7 @@ RSpec.describe 'Work package copy', js: true, selenium: true do
work_package_page.visit_tab! :relations
expect_angular_frontend_initialized
expect(page).to have_selector('.relation-group--header', text: 'RELATED TO', wait: 20)
expect(page).to have_selector('.wp-relations--subject-field', text: original_work_package.subject)
expect(page).to have_selector("[data-qa-selector='op-relation--row-subject']", text: original_work_package.subject)
end
describe 'when source work package has an attachment' do
@@ -181,6 +181,6 @@ RSpec.describe 'Work package copy', js: true, selenium: true do
work_package_page.visit_tab!('relations')
expect_angular_frontend_initialized
expect(page).to have_selector('.relation-group--header', text: 'RELATED TO', wait: 20)
expect(page).to have_selector('.wp-relations--subject-field', text: original_work_package.subject)
expect(page).to have_selector("[data-qa-selector='op-relation--row-subject']", text: original_work_package.subject)
end
end
@@ -69,14 +69,14 @@ shared_examples 'work package relations tab', js: true, selenium: true do
##
# Add child #1
relations.openChildrenAutocompleter
relations.open_children_autocompleter
relations.add_existing_child(child)
relations.expect_child(child)
##
# Add child #2
relations.openChildrenAutocompleter
relations.open_children_autocompleter
relations.add_existing_child(child2)
relations.expect_child(child2)
@@ -240,7 +240,7 @@ shared_examples 'work package relations tab', js: true, selenium: true do
##
# Add child
relations.openChildrenAutocompleter
relations.open_children_autocompleter
relations.add_existing_child(child)
wp_page.expect_and_dismiss_toaster(message: 'Successful update.')
@@ -249,7 +249,7 @@ describe 'Work package relations tab', js: true, selenium: true do
# Wait for the relations table to be present
sleep 2
expect(page).to have_selector('.wp-relations--subject-field')
expect(page).to have_selector("[data-qa-selector='op-relation--row-subject']")
scroll_to_element find('.detail-panel--relations')
@@ -0,0 +1,85 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2023 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
require 'spec_helper'
require Rails.root.join("db/migrate/20220428071221_restore_defaults_on_empty_settings.rb")
describe RestoreDefaultsOnEmptySettings, type: :model do
# Silencing migration logs, since we are not interested in that during testing
subject { ActiveRecord::Migration.suppress_messages { described_class.new.up } }
shared_examples_for "a successful migration of an empty setting" do
let(:setting_name) { raise "define me!" }
let(:old_value) { nil }
let(:expected_value) { raise "define me!" }
before do
Setting.create name: setting_name, value: ""
end
it 'migrates the value to the expected value' do
expect { subject }
.to change { Setting.find_by(name: setting_name).value }
.from(old_value)
.to(expected_value)
end
it 'does not raise a type error' do
expect { subject }.not_to raise_error(TypeError)
end
end
context "with an empty setting which must be an array" do
it_behaves_like "a successful migration of an empty setting" do
let(:setting_name) { "apiv3_cors_origins" }
let(:expected_value) { [] }
end
end
context "with an empty setting which must be a hash" do
it_behaves_like "a successful migration of an empty setting" do
let(:setting_name) { "ldap_tls_options" }
let(:expected_value) { {} }
end
end
context "with an empty setting which must be a string" do
it_behaves_like "a successful migration of an empty setting" do
let(:setting_name) { "default_language" }
let(:old_value) { "" }
let(:expected_value) { "en" }
end
end
context "with an empty setting which must be a boolean" do
it_behaves_like "a successful migration of an empty setting" do
let(:setting_name) { "smtp_enable_starttls_auto" }
let(:expected_value) { false }
end
end
end
+21
View File
@@ -358,6 +358,27 @@ describe Project do
end
end
describe '#active_subprojects' do
subject { root_project.active_subprojects }
shared_let(:root_project) { create(:project) }
shared_let(:parent_project) { create(:project, parent: root_project) }
shared_let(:child_project1) { create(:project, parent: parent_project) }
context 'with an archived subproject' do
before do
child_project1.active = false
child_project1.save
end
it { is_expected.to eq [parent_project] }
end
context 'with all active subprojects' do
it { is_expected.to eq [parent_project, child_project1] }
end
end
describe '#rolled_up_types' do
let!(:parent) do
create(:project, types: [parent_type]).tap do |p|
@@ -0,0 +1,127 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2023 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
require 'spec_helper'
describe Projects::ArchiveService do
let(:project) { create(:project) }
let(:subproject1) { create(:project) }
let(:subproject2) { create(:project) }
let(:subproject3) { create(:project) }
let(:user) { create(:admin) }
let(:instance) { described_class.new(user:, model: project) }
context 'with project without any subprojects' do
it 'archives the project' do
expect(project.reload).not_to be_archived
expect(instance.call).to be_truthy
expect(project.reload).to be_archived
end
end
context 'with project having subprojects' do
before do
project.update(children: [subproject1, subproject2, subproject3])
project.reload
end
shared_examples 'when archiving a project' do
it 'archives the project' do
# Baseline verification.
expect(project.reload).not_to be_archived
# Action.
expect(instance.call).to be_truthy
# Endline verification.
expect(project.reload).to be_archived
end
it 'archives all the subprojects' do
# Baseline verification.
expect(subproject1.reload).not_to be_archived
expect(subproject2.reload).not_to be_archived
expect(subproject3.reload).not_to be_archived
# Action.
expect(instance.call).to be_truthy
# Endline verification.
expect(subproject1.reload).to be_archived
expect(subproject2.reload).to be_archived
expect(subproject3.reload).to be_archived
end
end
include_examples 'when archiving a project'
context 'with deep nesting' do
before do
project.update(children: [subproject1])
subproject1.update(children: [subproject2])
subproject2.update(children: [subproject3])
project.reload
subproject1.reload
end
include_examples 'when archiving a project'
end
end
context 'with project having an archived subproject' do
let(:subproject1) { create(:project, active: false) }
before do
project.update(children: [subproject1, subproject2, subproject3])
project.reload
end
context 'while archiving the project' do
it 'does not change timestamp of the already archived subproject' do
expect(subproject1.reload).to be_archived
before_timestamp = subproject1.updated_at
expect(instance.call).to be_truthy
after_timestamp = subproject1.reload.updated_at
expect(before_timestamp).to eq(after_timestamp)
end
it 'changes timestamp of the active subproject' do
expect(subproject2.reload).not_to be_archived
before_timestamp = subproject2.updated_at
expect(instance.call).to be_truthy
after_timestamp = subproject2.reload.updated_at
expect(before_timestamp).not_to eq(after_timestamp)
end
end
end
end
@@ -76,6 +76,48 @@ describe Users::RegisterUserService do
end
end
describe '#register_omniauth_user' do
let(:user) { User.new(status: Principal.statuses[:registered], identity_url: 'azure:1234') }
let(:instance) { described_class.new(user) }
let(:call) { instance.call }
before do
allow(user).to receive(:activate)
allow(user).to receive(:save).and_return true
# required so that the azure provider is visible (ee feature)
allow(EnterpriseToken).to receive(:show_banners?).and_return false
with_all_registration_options do |_type|
call
end
end
it 'tries to activate that user regardless of settings' do
expect(call).to be_success
expect(call.result).to eq user
expect(call.message).to eq I18n.t(:notice_account_registered_and_logged_in)
end
context(
'with limit_self_registration enabled and self_registration disabled',
with_settings: {
self_registration: 0,
plugin_openproject_openid_connect: {
providers: {
azure: { identifier: "foo", secret: "bar", limit_self_registration: true }
}
}
}
) do
it 'fails to activate due to disabled self registration' do
expect(call).not_to be_success
expect(call.result).to eq user
expect(call.message).to eq I18n.t('account.error_self_registration_disabled')
end
end
end
describe '#ensure_registration_allowed!' do
it 'returns an error for disabled' do
allow(Setting).to receive(:self_registration).and_return(0)
@@ -48,7 +48,7 @@ module Components
def click_relation(relatable)
SeleniumHubWaiter.wait
page.find(".relation-row-#{relatable.id} .wp-relations--subject-field").click
page.find(".relation-row-#{relatable.id} [data-qa-selector='op-relation--row-id']").click
end
def edit_relation_type(relatable, to_type:)
@@ -63,7 +63,7 @@ module Components
def hover_action(relatable, action)
retry_block do
# Focus type edit to expose buttons
span = page.find(".relation-row-#{relatable.id} .relation-row--type", wait: 20)
span = page.find(".relation-row-#{relatable.id} [data-qa-selector='op-relation--row-type']", wait: 20)
scroll_to_element(span)
page.driver.browser.action.move_to(span.native).perform
@@ -96,7 +96,7 @@ module Components
container = find('.wp-relations-create--form', wait: 10)
# Labels to expect
relation_label = I18n.t('js.relation_labels.' + type)
relation_label = I18n.t("js.relation_labels.#{type}")
select relation_label, from: 'relation-type--select'
@@ -111,9 +111,9 @@ module Components
text: relation_label.upcase,
wait: 10)
expect(page).to have_selector('.relation-row--type', text: to.type.name.upcase)
expect(page).to have_selector("[data-qa-selector='op-relation--row-type']", text: to.type.name.upcase)
expect(page).to have_selector('.wp-relations--subject-field', text: to.subject)
expect(page).to have_selector("[data-qa-selector='op-relation--row-subject']", text: to.subject)
## Test if relation exist
work_package.reload
@@ -123,15 +123,15 @@ module Components
end
def expect_relation(relatable)
expect(relations_group).to have_selector('.wp-relations--subject-field', text: relatable.subject)
expect(relations_group).to have_selector("[data-qa-selector='op-relation--row-subject']", text: relatable.subject)
end
def expect_relation_by_text(text)
expect(relations_group).to have_selector('.wp-relations--subject-field', text:)
expect(relations_group).to have_selector("[data-qa-selector='op-relation--row-subject']", text:)
end
def expect_no_relation(relatable)
expect(page).not_to have_selector('.wp-relations--subject-field', text: relatable.subject)
expect(page).not_to have_selector("[data-qa-selector='op-relation--row-subject']", text: relatable.subject)
end
def add_parent(query, work_package)
@@ -172,7 +172,7 @@ module Components
subject.update subject_text
end
def openChildrenAutocompleter
def open_children_autocompleter
retry_block do
next if page.has_selector?('.wp-relations--children .ng-input input')