mirror of
https://github.com/opf/openproject.git
synced 2026-06-13 19:20:00 +00:00
Merge remote-tracking branch 'origin/release/12.5' into dev
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
#
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
## 
|
||||

|
||||
|
||||
@@ -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 [];
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -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);
|
||||
}
|
||||
|
||||
+84
-57
@@ -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>
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="content"
|
||||
class="relation-container"
|
||||
*ngIf="relatedWorkPackages"
|
||||
>
|
||||
<wp-relation-row
|
||||
|
||||
+37
-4
@@ -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)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -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%
|
||||
|
||||
@@ -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
|
||||
+45
@@ -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 d’OpenID
|
||||
providers:
|
||||
@@ -17,3 +18,6 @@ ca:
|
||||
no_results_table: Encara no s'han definit cap proveïdor.
|
||||
plural: Proveïdors d’OpenID
|
||||
singular: Proveïdor d’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 @@ 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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
@@ -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')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user