diff --git a/app/components/activities/item_component.html.erb b/app/components/activities/item_component.html.erb index 540337fc93d9..ba689f97991e 100644 --- a/app/components/activities/item_component.html.erb +++ b/app/components/activities/item_component.html.erb @@ -65,7 +65,7 @@ See COPYRIGHT and LICENSE files for more details.
  • <%= detail %>
  • <% end -%> <% if time_entry_url.present? -%> -
  • <%= link_to "Details", @event.event_url %>
  • +
  • <%= link_to I18n.t(:label_details), @event.event_url %>
  • <% end -%> <% elsif noop? -%> diff --git a/app/components/work_packages/activities_tab/index_component.rb b/app/components/work_packages/activities_tab/index_component.rb index acad2fe5006e..92f4cdad2b64 100644 --- a/app/components/work_packages/activities_tab/index_component.rb +++ b/app/components/work_packages/activities_tab/index_component.rb @@ -35,16 +35,17 @@ class IndexComponent < ApplicationComponent include OpPrimer::ComponentHelpers include OpTurbo::Streamable - def initialize(work_package:, filter: :all) + def initialize(work_package:, last_server_timestamp:, filter: :all) super @work_package = work_package @filter = filter + @last_server_timestamp = last_server_timestamp end private - attr_reader :work_package, :filter + attr_reader :work_package, :filter, :last_server_timestamp def wrapper_data_attributes { @@ -59,7 +60,8 @@ def wrapper_data_attributes "work-packages--activities-tab--index-user-id-value": User.current.id, "work-packages--activities-tab--index-work-package-id-value": work_package.id, "work-packages--activities-tab--index-polling-interval-in-ms-value": polling_interval, - "work-packages--activities-tab--index-notification-center-path-name-value": notifications_path + "work-packages--activities-tab--index-notification-center-path-name-value": notifications_path, + "work-packages--activities-tab--index-last-server-timestamp-value": last_server_timestamp } end diff --git a/app/components/work_packages/activities_tab/journals/index_component.rb b/app/components/work_packages/activities_tab/journals/index_component.rb index 582432c9440d..a5f5f9b84adf 100644 --- a/app/components/work_packages/activities_tab/journals/index_component.rb +++ b/app/components/work_packages/activities_tab/journals/index_component.rb @@ -64,7 +64,11 @@ def journal_sorting_desc? end def journals - work_package.journals.includes(:user, :notifications).reorder(version: journal_sorting) + work_package + .journals + .includes(:user, :notifications) + .reorder(version: journal_sorting) + .with_sequence_version end def journal_with_notes diff --git a/app/components/work_packages/activities_tab/journals/item_component.html.erb b/app/components/work_packages/activities_tab/journals/item_component.html.erb index 0e87fad9783d..784a730cb55c 100644 --- a/app/components/work_packages/activities_tab/journals/item_component.html.erb +++ b/app/components/work_packages/activities_tab/journals/item_component.html.erb @@ -4,7 +4,7 @@ if show_comment_container? journal_container.with_row do render(border_box_container( - id: "activity-anchor-#{journal.version}", + id: "activity-anchor-#{journal.sequence_version}", padding: :condensed, "aria-label": I18n.t("activities.work_packages.activity_tab.commented") )) do |border_box_component| @@ -52,10 +52,10 @@ data: { turbo: false, action: "click->work-packages--activities-tab--index#setAnchor:prevent", - "work-packages--activities-tab--index-id-param": journal.version + "work-packages--activities-tab--index-id-param": journal.sequence_version } )) do - "##{journal.version}" + "##{journal.sequence_version}" end end header_end_container.with_column(ml: 1, diff --git a/app/components/work_packages/activities_tab/journals/item_component.rb b/app/components/work_packages/activities_tab/journals/item_component.rb index 1d20546545bc..7439943c8680 100644 --- a/app/components/work_packages/activities_tab/journals/item_component.rb +++ b/app/components/work_packages/activities_tab/journals/item_component.rb @@ -73,7 +73,7 @@ def activity_url end def activity_anchor - "#activity-#{journal.version}" + "#activity-#{journal.sequence_version}" end def updated? diff --git a/app/components/work_packages/activities_tab/journals/item_component/details.html.erb b/app/components/work_packages/activities_tab/journals/item_component/details.html.erb index 04e3144a0aa2..f8b19b5d4073 100644 --- a/app/components/work_packages/activities_tab/journals/item_component/details.html.erb +++ b/app/components/work_packages/activities_tab/journals/item_component/details.html.erb @@ -1,6 +1,11 @@ <%= component_wrapper(class: "work-packages-activities-tab-journals-item-component-details") do - flex_layout(my: 0, border: :left, classes: "work-packages-activities-tab-journals-item-component-details--journal-details-container") do |details_container| + flex_layout( + my: 0, + border: :left, + classes: "work-packages-activities-tab-journals-item-component-details--journal-details-container", + data: { initial: journal.initial? } + ) do |details_container| case filter when :only_comments render_empty_line(details_container) unless journal.notes.blank? && !journal.noop? diff --git a/app/components/work_packages/activities_tab/journals/item_component/details.rb b/app/components/work_packages/activities_tab/journals/item_component/details.rb index 79c8bf8830ef..7734ec774b95 100644 --- a/app/components/work_packages/activities_tab/journals/item_component/details.rb +++ b/app/components/work_packages/activities_tab/journals/item_component/details.rb @@ -58,7 +58,7 @@ def render_details_header(details_container) flex_layout: true, justify_content: :space_between, classes: "work-packages-activities-tab-journals-item-component-details--journal-details-header-container", - id: "activity-anchor-#{journal.version}" + id: "activity-anchor-#{journal.sequence_version}" ) do |header_container| render_header_start(header_container) render_header_end(header_container) @@ -192,9 +192,9 @@ def render_activity_link(container) underline: false, font_size: :small, data: { turbo: false, action: "click->work-packages--activities-tab--index#setAnchor:prevent", - "work-packages--activities-tab--index-id-param": journal.version } + "work-packages--activities-tab--index-id-param": journal.sequence_version } )) do - "##{journal.version}" + "##{journal.sequence_version}" end end end diff --git a/app/components/work_packages/activities_tab/journals/item_component/details.sass b/app/components/work_packages/activities_tab/journals/item_component/details.sass index 26c4b535a6a4..9aaa0e6ca65d 100644 --- a/app/components/work_packages/activities_tab/journals/item_component/details.sass +++ b/app/components/work_packages/activities_tab/journals/item_component/details.sass @@ -2,6 +2,11 @@ &--journal-details-container margin-left: 19px min-height: 20px + // applied by the stimulus controller in specific UI states + &--border-removed + border-left: none!important + &--hidden + display: none!important &--journal-details-container--empty--last--asc display: none!important &--journal-details-header-container diff --git a/app/controllers/work_packages/activities_tab_controller.rb b/app/controllers/work_packages/activities_tab_controller.rb index ab1b16aafb71..98d7c3ef6b7c 100644 --- a/app/controllers/work_packages/activities_tab_controller.rb +++ b/app/controllers/work_packages/activities_tab_controller.rb @@ -42,31 +42,25 @@ def index render( WorkPackages::ActivitiesTab::IndexComponent.new( work_package: @work_package, - filter: @filter + filter: @filter, + last_server_timestamp: get_current_server_timestamp ), layout: false ) end def update_streams + set_last_server_timestamp_to_headers + perform_update_streams_from_last_update_timestamp respond_with_turbo_streams end def update_filter - update_via_turbo_stream( - component: WorkPackages::ActivitiesTab::Journals::FilterAndSortingComponent.new( - work_package: @work_package, - filter: @filter - ) - ) - update_via_turbo_stream( - component: WorkPackages::ActivitiesTab::Journals::IndexComponent.new( - work_package: @work_package, - filter: @filter - ) - ) + # update the whole tab to reflect the new filtering in all components + # we need to call replace in order to properly re-init the index stimulus component + replace_whole_tab respond_with_turbo_streams end @@ -115,6 +109,7 @@ def create call = create_journal_service_call if call.success? && call.result + set_last_server_timestamp_to_headers handle_successful_create_call(call) else handle_failed_create_call(call) # errors should be rendered in the form @@ -213,7 +208,9 @@ def find_project end def find_journal - @journal = Journal.find(params[:id]) + @journal = Journal + .with_sequence_version + .find(params[:id]) rescue ActiveRecord::RecordNotFound respond_with_error(I18n.t("label_not_found")) end @@ -284,7 +281,8 @@ def replace_whole_tab replace_via_turbo_stream( component: WorkPackages::ActivitiesTab::IndexComponent.new( work_package: @work_package, - filter: @filter + filter: @filter, + last_server_timestamp: get_current_server_timestamp ) ) end @@ -309,7 +307,9 @@ def create_journal_service_call end def generate_time_based_update_streams(last_update_timestamp) - journals = @work_package.journals + journals = @work_package + .journals + .with_sequence_version if @filter == :only_comments journals = journals.where.not(notes: "") @@ -427,4 +427,14 @@ def grouped_emoji_reactions_for_journal def allowed_to_edit?(journal) journal.editable_by?(User.current) end + + def get_current_server_timestamp + # single source of truth for the server timestamp format + Time.current.iso8601(3) + end + + def set_last_server_timestamp_to_headers + # Add server timestamp to response in order to let the client be in sync with the server + response.headers["X-Server-Timestamp"] = get_current_server_timestamp + end end diff --git a/app/models/journal.rb b/app/models/journal.rb index cbb216b1eaa4..04f66834916a 100644 --- a/app/models/journal.rb +++ b/app/models/journal.rb @@ -102,6 +102,10 @@ class Journal < ApplicationRecord has_many :notifications, dependent: :destroy + include ::Scopes::Scoped + + scopes :with_sequence_version + # Scopes to all journals excluding the initial journal - useful for change # logs like the history on issue#show scope :changing, -> { where(["version > 1"]) } diff --git a/app/models/journals/scopes/with_sequence_version.rb b/app/models/journals/scopes/with_sequence_version.rb new file mode 100644 index 000000000000..b806a377cea5 --- /dev/null +++ b/app/models/journals/scopes/with_sequence_version.rb @@ -0,0 +1,55 @@ +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2010-2024 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 Journals::Scopes + module WithSequenceVersion + extend ActiveSupport::Concern + + class_methods do + def with_sequence_version + joins( + <<~SQL.squish + JOIN LATERAL ( + SELECT + journals_rank.id, + ROW_NUMBER() OVER (ORDER BY version ASC) sequence_version + FROM + journals journals_rank + WHERE + journals_rank.journable_id = journals.journable_id + AND + journals_rank.journable_type = journals.journable_type + ) ranked + ON ranked.id = journals.id + SQL + ) + .select("*") + end + end + end +end diff --git a/docs/development/development-environment/docker/README.md b/docs/development/development-environment/docker/README.md index e9f54ab237dc..a16792714f2e 100644 --- a/docs/development/development-environment/docker/README.md +++ b/docs/development/development-environment/docker/README.md @@ -466,7 +466,7 @@ Once the keycloak service is started and running, you can access the keycloak in and login with initial username and password as `admin`. Keycloak being an OpenID connect provider, we need to setup an OIDC integration for OpenProject. -[Setup OIDC (keycloak) integration for OpenProject](../../../installation-and-operations/misc/custom-openid-connect-providers/#keycloak) +[Setup OIDC (keycloak) integration for OpenProject](../../../system-admin-guide/authentication/openid-providers/) Once the above setup is completed, In the root `docker-compose.override.yml` file, uncomment all the environment in `backend` service for keycloak and set the values according to configuration done in keycloak for OpenProject Integration. diff --git a/docs/installation-and-operations/misc/custom-openid-connect-providers/README.md b/docs/installation-and-operations/misc/custom-openid-connect-providers/README.md index ffda2ec0a781..fe442ea23ae3 100644 --- a/docs/installation-and-operations/misc/custom-openid-connect-providers/README.md +++ b/docs/installation-and-operations/misc/custom-openid-connect-providers/README.md @@ -1,314 +1,9 @@ # Custom OpenID Connect providers -OpenProject's admin interface only allows you to configure providers from a pre-defined list. -This includes Google Workspace and Microsoft Azure Active Directory right now. Find out how to use those in the [OpenID Providers Authentication Guide](../../../system-admin-guide/authentication/openid-providers/). +> [!IMPORTANT] +> OpenID Connect providers is an Enterprise add-on. If you do not see the button you will have to activate the Enterprise edition first. -You can still use an arbitrary provider. But for the time being there is no user interface for this. -That means you will have to do it directly using the console on the server or via environment variables. +Starting in OpenProject 15.0., you can create custom OpenID Connect providers with the user interface [OpenID Providers Authentication Guide](../../../system-admin-guide/authentication/openid-providers/). -> **Warning**: Only do this if you know what you are doing. This may break your existing OpenID Connect authentication or cause other issues otherwise. +Please use this document for references on all configuration options. Any providers you have created in earlier versions will have been migrated and should be available from the user interface. -First start the console. - -```shell -sudo openproject run console -# if user the docker all-in-one container: docker exec -it openproject bundle exec rails console -# if using docker-compose: docker-compose run --rm web bundle exec rails console -``` - -Once in the console you can change the `plugin_openproject_openid_connect` setting -directly to configure arbitrary providers. - -Next define the settings for your custom provider. In this example we are configuring Okta: - -```ruby -options = { - "display_name"=>"Okta", - "host"=>"mypersonal.okta.com", - "identifier"=>"", - "secret"=>"", - "authorization_endpoint" => "/oauth2/v1/authorize", - "token_endpoint" => "/oauth2/v1/token", - "userinfo_endpoint" => "/oauth2/v1/userinfo", - "end_session_endpoint" => "https://mypersonal.okta.com/oauth2/{authorizationServerId}/v1/logout" -} -``` - -For Keycloak, settings similar to the following would be used: - -```ruby -options = { - "display_name"=>"Keycloak", - "host"=>"keycloak.example.com", - "identifier"=>"", - "secret"=>"", - "authorization_endpoint" => "/auth/realms/REALM/protocol/openid-connect/auth", - "token_endpoint" => "/auth/realms/REALM/protocol/openid-connect/token", - "userinfo_endpoint" => "/auth/realms/REALM/protocol/openid-connect/userinfo" -} -``` - -Just type this into the console and confirm by pressing *Enter*. - -This assumes that you have configured your application in the respective provider correctly -including the **redirect URL** which will be the following: - -```ruby -"#{Setting.protocol}://#{Setting.host_name}/auth/okta/callback" -``` - -You can copy that into the console to get the URL you need. - -Finally you can the write the actual setting like this: - -```ruby -Setting.plugin_openproject_openid_connect = Hash(Setting.plugin_openproject_openid_connect || {}).deep_merge({ - "providers" => { - "okta" => options - } -}) -``` - -Replace "okta" with any other value such as "keycloak". It is used as the identifier in some URLs so keep it a plain lowercase string. - -Just copy these lines into the console and again confirm using *Enter*. -After you are done you can leave the console by entering `exit`. - -Once this is done you will see an "Okta" button in the bottom area of the login form. -Clicking on it will start the login process. - -_**Note**: This is an Enterprise add-on. If you do not see the button you will have to activate the Enterprise edition first._ - -## Environment variables - -Rather than setting these options via the rails console, you can also define them through the -[OpenProject configuration](../../configuration/) which can -also be defined through -[environment variables](../../configuration/environment/). - -The variable names can be derived from the options seen above. All variables will start with the prefix -`OPENPROJECT_OPENID__CONNECT_` followed by the provider name. For instance the okta example from above would -be defined via environment variables like this: - -```shell -OPENPROJECT_OPENID__CONNECT_OKTA_DISPLAY__NAME="Okta" -OPENPROJECT_OPENID__CONNECT_OKTA_HOST="mypersonal.okta.com" -OPENPROJECT_OPENID__CONNECT_OKTA_IDENTIFIER="" -# etc. -``` - -**Note**: Underscores in option names must be escaped by doubling them. So make sure to really do use two consecutive -underscores in `DISPLAY__NAME`, `TOKEN__ENDPOINT` and so forth. - -## More options - -You can see a list of possible options [here](https://github.com/m0n9oose/omniauth_openid_connect#options-overview). - -### Known providers and multiple connection per provider - -There are a number of known providers where the endpoints are configured automatically based on the provider name in the configuration. All that is required are the client ID (identifier) and secret in that case. - -If you want to configure multiple connections using the same provider you can prefix an arbitrary name with the -provider name followed by a period. For instance, if you want to configure 2 AzureAD connections and 1 Google connection it would look like this: - -```ruby -Setting.plugin_openproject_openid_connect = Hash(Setting.plugin_openproject_openid_connect || {}).deep_merge({ - "providers" => { - "azure.dept1" => { "display_name"=>"Department 1","identifier"=>"...","secret"=>"..." }, - "azure.dept2" => { "display_name"=>"Department 2","identifier"=>"...","secret"=>"..." }, - "google" => { "display_name"=>"Google","identifier"=>"...","secret"=>"..." } - } -}) -``` - -At the time of writing the known providers are: `azure`, `google`, `okta` - -### Attribute mapping - -You can override the default attribute mapping for values derived from the userinfo endpoint. For example, let's map the OpenProject login to the claim `preferred_username` that is sent by many OIDC providers. - -```ruby -options = { - # ... other options - attribute_map: { - 'login' => 'preferred_username' - } -} -``` - -### Back-channel logout - -OpenProject OIDC integration supports [back-channel logouts](https://openid.net/specs/openid-connect-backchannel-1_0.html) if OpenProject is configured for ActiveRecord based sessions (which is the default). - -On the identity provider side, you need to set `https:///auth//backchannel-logout`. `` 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](../../../system-admin-guide/authentication/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. -Mind though that currently only claims requested for the id_token returned with the authorize response are validated. -That is authentication will fail if a requested essential claim is not returned. - -#### Requesting MFA authentication via the ACR claim - -Say for example that you want to request that the user authenticate using MFA (multi-factor authentication). -You can do this by using the ACR (Authentication Context Class Reference) claim. - -This may look different for each identity provider. But if they follow, for instance the [EAP (Extended Authentication Profile)](https://openid.net/specs/openid-connect-eap-acr-values-1_0.html) then the claims would be `phr` (phishing-resistant) and 'phrh' (phishing-resistant hardware-protected). Others may simply have an additional claim called `Multi_Factor`. - -You have to check with your identity provider how these values must be called. - -In the following example we request a list of ACR values. One of which must be satisfied -(i.e. returned in the ID token by the identity provider, meaning that the requested authentication mechanism was used) -for the login in OpenProject to succeed. If none of the requested claims are present, authentication will fail. - -```ruby -options = { ... } - -options["claims"] = { - "id_token": { - "acr": { - "essential": true, - "values": ["phr", "phrh", "Multi_Factor"] - } - } -} -``` - -#### Non-essential claims - -You may also request non-essential claims. In the example above this indicates that users should preferably be authenticated using -those mechanisms but it's not strictly required. The login into OpenProject will then work even if none of the claims -are returned by the identity provider. - -**The acr_values option** - -For non-essential ACR claims you can also use the shorthand form of the option like this: - -```ruby -options = { ... } - -options["acr_values"] = "phr phrh Multi_Factor" -``` - -The option takes a space-separated list of ACR values. This is functionally the same as using the -more complicated `claims` option above but with `"essential": false`. - -For all other claims there is no such shorthand. - -## Instructions for common OIDC providers - -The following section contains instructions for common OpenID Connect providers. Feel free to contribute your settings through the editing functionality at the bottom of this page. - -### Keycloak - -In Keycloak, use the following steps to set up a OIDC integration for OpenProject: - -- Select or create a realm you want to authenticate OpenProject with. Remember that realm identifier. For the remainder of this section, we're using REALM as the placeholder you'll need to replace. -- Under "Clients" menu, click on "Create" or "Create client" -- **Add client**: Enter the following details - - **Client type / protocol**: OpenID Connect - - **Client ID**: `https://` - - **Name**: Choose any name, used only within keycloak -- For the **Capability config**, keep Standard flow checked. In our tested version of Keycloak, this was the default. -- Click on Save - -You will be forwarded to the settings tab of the new client. Change these settings: - -- Set **Valid redirect URIs** to `https:///auth/keycloak/*` -- Enable **Sign Documents** -- If you want to enable [Backchannel logout](https://openid.net/specs/openid-connect-backchannel-1_0.html), set **Backchannel logout URL** to `https:///auth/keycloak/backchannel-logout` - -Next, you will need to create or note down the client secret for that client. - -- Go to the **Credentials** tab -- Click on the copy to clipboard button next to **Client secret** to copy that value - -**OPTIONAL:** By default, OpenProject will map the user's email to the login attribute in OpenProject. If you want to change that, you can do it by providing an alternate claim value in Keycloak: - -- Go to **Client scopes** -- Click on the `https://-dedicated` scope -- Click on **Add mapper** and **By configuration** -- Select **User property** -- Assuming you want to provide the username as `preferred_username` to OpenProject, set these values. This will depend on what attribute you want to map: - - Set name and to `username` - - Set Token claim name to `preferred_username` -- Click on **Save** - -#### Setting up OpenProject for Keycloak integration - -In OpenProject, these are the variables you will need to set. Please refer to the above documentation for the different ways you can configure these variables: - -```shell -# The name of the login button in OpenProject, you can freely set this to anything you like -OPENPROJECT_OPENID__CONNECT_KEYCLOAK_DISPLAY__NAME="Keycloak" -OPENPROJECT_OPENID__CONNECT_KEYCLOAK_HOST="" -OPENPROJECT_OPENID__CONNECT_KEYCLOAK_IDENTIFIER="https://" -OPENPROJECT_OPENID__CONNECT_KEYCLOAK_SECRET="" -OPENPROJECT_OPENID__CONNECT_KEYCLOAK_ISSUER="https:///realms/" -OPENPROJECT_OPENID__CONNECT_KEYCLOAK_AUTHORIZATION__ENDPOINT="/realms//protocol/openid-connect/auth" -OPENPROJECT_OPENID__CONNECT_KEYCLOAK_TOKEN__ENDPOINT="/realms//protocol/openid-connect/token" -OPENPROJECT_OPENID__CONNECT_KEYCLOAK_USERINFO__ENDPOINT="/realms//protocol/openid-connect/userinfo" -OPENPROJECT_OPENID__CONNECT_KEYCLOAK_END__SESSION__ENDPOINT="http:///realms//protocol/openid-connect/logout" -# Optional, if you have created the client scope mapper as shown above -# OPENPROJECT_OPENID__CONNECT_KEYCLOAK_ATTRIBUTE__MAP_LOGIN="preferred_username" -``` - -### Azure with Microsoft Graph API - -The Azure integration for OpenProject uses the previous userinfo endpoints, which for some tenants results in not being able to access the user's email attribute. [See this bug report for more information](https://community.openproject.org/projects/openproject/work_packages/45832). While our UI is still being extended to accept the new endpoints, you can manually configure Azure like follows. - -**What you need from Azure** - -Use our [Azure Active Directory guide](../../../system-admin-guide/authentication/openid-providers/#azure-active-directory) to create the OpenProject client and note down these values - -- The Client ID you set up for OpenProject (assumed to be `https://`) -- The client secret -- The tenant's UUID ([Please see this guide](https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-protocols-oidc) for more information on the tenant value) - -#### Setting up OpenProject for Keycloak integration - -In OpenProject, these are the variables you will need to set. Please refer to the above documentation for the different ways you can configure these variables: - -```shell -openproject config:set OPENPROJECT_OPENID__CONNECT_AZURE_DISPLAY__NAME="Azure" -openproject config:set OPENPROJECT_OPENID__CONNECT_AZURE_HOST="login.microsoftonline.com" -openproject config:set OPENPROJECT_OPENID__CONNECT_AZURE_IDENTIFIER="https://" -openproject config:set OPENPROJECT_OPENID__CONNECT_AZURE_SECRET="" -openproject config:set OPENPROJECT_OPENID__CONNECT_AZURE_AUTHORIZATION__ENDPOINT="https://login.microsoftonline.com/%3CUUID%3E/oauth2/v2.0/authorize" -openproject config:set OPENPROJECT_OPENID__CONNECT_AZURE_TOKEN__ENDPOINT="https://login.microsoftonline.com/%3CUUID%3E/oauth2/v2.0/token" -openproject config:set OPENPROJECT_OPENID__CONNECT_AZURE_USERINFO__ENDPOINT="https://graph.microsoft.com/oidc/userinfo" -``` - -Restart your OpenProject server and test the login button to see if it works. - -## Troubleshooting - -**Q: After clicking on a provider badge, I am redirected to a signup form that says a user already exists with that login.** - -A: This can happen if you previously created user accounts in OpenProject with the same email than what is stored in the identity provider. In this case, if you want to allow existing users to be automatically remapped to the OIDC provider, you should do the following: - -Spawn an interactive console in OpenProject. The following example shows the command for the packaged installation. -See [our process control guide](../../../installation-and-operations/operation/control/) for information on other installation types. - -```shell -sudo openproject run console -> Setting.oauth_allow_remapping_of_existing_users = true -> exit -``` - -Then, existing users should be able to log in using their OIDC identity. Note that this works only if the user is using password-based authentication, and is not linked to any other authentication source (e.g. LDAP) or identity provider. - -Note that this setting is set to true by default for new installations already. diff --git a/docs/release-notes/15-0-0/README.md b/docs/release-notes/15-0-0/README.md index bdd541c62ad9..5ec88aa01e2b 100644 --- a/docs/release-notes/15-0-0/README.md +++ b/docs/release-notes/15-0-0/README.md @@ -10,7 +10,7 @@ release_date: 2024-10-31 Release date: 2024-11-13 -We released [OpenProject 15.0.0](https://community.openproject.org/versions/2076). The major release contains several bug fixes and we recommend updating to the newest version. In these Release Notes, we will give an overview of important feature changes. At the end, you will find a complete list of all changes and bug fixes. +We released [OpenProject 15.0.0](https://community.openproject.org/versions/2076). This major release introduces a new timeline for work package activities with emoji reactions, adds a user interface for configuring SSO providers, and more improvements. With over 50 bugs fixed, we recommend you update OpenProject. In these Release Notes, we will give an overview of important feature changes. At the end, you will find a complete list of all changes and bug fixes. ## Important feature changes @@ -50,9 +50,9 @@ Please note that emoji reactions don't trigger notifications. If you need your c Starting with version 15.0, the notification center will continuously update and new notifications will appear directly. This means no more blue flash message mentioning that there are updates and asking if you want to reload the page. The number shown next to the bell icon will also update immediately. This feature adds to our goal to enable smooth communication and information. -### Admin interfaces for SAML and OIDC within the Enterprise Cloud version +### Admin interfaces for SAML and OpenID Connect (Enterprise only) -With OpenProject 15.0, SaaS customers will benefit from our new user interface for SAML and OIDC. This means they can now set up integrations between OpenProject and SAML or OpenID connect stacks independently and offer users options for Single Sign-On (SSO). Before 15.0, SaaS customers had to contact the OpenProject support if they wanted custom integrations with their SAML or OpenID connect stacks. Now, they cannot only set them up on their own, but also have tools for debugging them if needed. +OpenProject has for a long time supported SAML and OpenID Connect configured through settings or environment variables. With OpenProject 15.0, all enterprise customers will benefit from our new user interface for SAML and OIDC. This means they can now set up integrations between OpenProject and SAML or OpenID connect stacks independently and offer users options for Single Sign-On (SSO). Before 15.0, SaaS customers had to contact the OpenProject support if they wanted custom integrations with their SAML or OpenID connect providers. These new interfaces greatly improve the user experience of adding these providers, which had been a hurdle at the beginning of your work with OpenProject. Related features in 15.0: @@ -71,15 +71,17 @@ Once set up, users can log in with their existing account, for example like show ### A new 'Standard global role' with permissions to view email addresses -With OpenProject 15.0, you get a new default 'Standard global role' that is automatically and permanently given to all users. If you are an administrator responsible for roles, please check this under *Administration > Users and permissions > Roles and permissions > Standard global role*. This role has several permissions to choose from, one being 'View users' mail addresses'. Enable this permission to allow any user to see everyone's email address in autocomplete situations, such as when they select a work package assignee from a drop-down list. +With OpenProject 15.0, a new default 'Standard global role' is automatically and permanently given to all users. If you are an administrator responsible for roles, please check this under *Administration > Users and permissions > Roles and permissions > Standard global role*. This role has several permissions to choose from, one being 'View users' mail addresses'. Enable this permission to allow any user to see everyone's email address in autocomplete situations, such as when they select a work package assignee from a drop-down list. -Before version 15.0, users could choose whether their email address was displayed. Now this is an administrative decision that applies to either everyone or no one. +Before version 15.0, users could choose whether their email address was displayed. Now this is an administrative decision that applies to either everyone or no one. The reason behind this is that this privacy control should be decided at the organizational level rather than individually. + +As a default, this new role does not have any permissions selected, so there is no change in the granted permissions due to the update. ![Example screenshot of permissions view for the new Standard global role, with checkmark at 'View users' mail addresses](openproject-15-0-standard-global-role.png) ### Quick action table headers in project lists for easier navigation -With OpenProject 15.0, we are pleased to release another great feature for our project lists: Clicking on the table headers in a project list now gives you a quick action menu that not only allows you to sort in descending or ascending order, but also to filter or change, remove or add a column. While these features are not new and you can still find them in the top-right menu button, these actions are now much quicker to access. +With OpenProject 15.0, we are pleased to release another great improvement for our project lists: Clicking on the table headers in a project list now gives you a quick action menu that not only allows you to sort in descending or ascending order, but also to filter or change, remove or add a column. You can still find the same actions in the top-right menu button, but now these actions are now much quicker to access. ![Example screenshot of a project list with dropdown menu on a table header](openproject-15-0-project-lists.png) @@ -108,7 +110,7 @@ In the personal menu that can be accessed by clicking on your avatar, we renamed ### Change the basic work package hovercard to Primer design system -If you hover over a linked work package, e.g. in a work package description or in a wiki entry, you will now see a new hovercard, following the Primer design system. This hovercard displays the most important information, like type, ID or status of the work package along with the assignee and their avatar. Here is an example of how this might look like now: +If you hover over a linked work package, e.g. in a work package description or in a wiki entry, you will now see a new hovercard, following the Primer design system. This card displays the most important information, like type, ID or status of the work package along with the assignee and their avatar. Here is an example of how this might look like now: ![Example screenshot showing a work package hovercard](openproject-15-0-hovercard.png) @@ -118,6 +120,23 @@ There have been some design improvements regarding meetings again. The work pack +## Important technical updates + +### SAML and OpenID connect providers + +With the introduction of the user interface for SAML and OpenID connect providers, the previous settings-based configuration has been deprecated. All existing providers that you created with these settings have been automatically converted into the UI element. + +To modify or update your configuration, please visit *Administration* -> *Authentication* -> *SAML providers* or *OpenID providers*. If you experience issues with your configuration after your update, please step through the configuration in the administration and confirm the settings. If you experience new issues on the connection to your providers after upgrading, please do not hesitate to reach out to our support team. + +If you configured your provider using environment variables (e.g., in Docker-based or through the Helm-chart values), this configuration option remains. The configured provider will still appear in the user interface, but is marked read-only. If you need to modify the environment variables, you need to make sure the seed rake task has been run. + +For more information, please see our updated guides on these topics: + +- [Connecting your OpenID connect provider to OpenProject](../../system-admin-guide/authentication/openid-providers/) +- [Connecting your SAML identity provider to OpenProject](../../system-admin-guide/authentication/saml/) + + + ## Bug fixes and changes diff --git a/docs/system-admin-guide/authentication/authentication-faq/README.md b/docs/system-admin-guide/authentication/authentication-faq/README.md index 451636ac081e..157cfa6ac209 100644 --- a/docs/system-admin-guide/authentication/authentication-faq/README.md +++ b/docs/system-admin-guide/authentication/authentication-faq/README.md @@ -46,8 +46,8 @@ We support all authentication providers that support the SAML and OpenID Connec ## Is it possible to use a custom SSO provider (e.g. Keycloak) with the Enterprise cloud edition? -It is possible to use Keycloak, but you can't configure it yourself at the moment as there's no user interface (UI) for custom SSO providers. We can set up the custom provider for you. Then you can access and edit it in the administration. You will be able to enter client ID and client secret via the OpenProject UI. -For context: The connection of custom SSO providers is also described [here](../../../installation-and-operations/misc/custom-openid-connect-providers/#custom-openid-connect-providers) (however, we would enter this configuration for your Enterprise cloud environment). +It is possible to use Keycloak, Okta, or other OpenID Connect providers with the user interface (UI) for custom SSO providers. +For context: The connection of custom SSO providers is also described [here](../openid-providers/). ## I want to connect AD and LDAP to OpenProject. Which attribute for authentication sources does OpenProject use? diff --git a/docs/system-admin-guide/authentication/openid-providers/README.md b/docs/system-admin-guide/authentication/openid-providers/README.md index e8120fe018a1..0c226cb635e5 100644 --- a/docs/system-admin-guide/authentication/openid-providers/README.md +++ b/docs/system-admin-guide/authentication/openid-providers/README.md @@ -9,48 +9,38 @@ keywords: OpenID providers | Topic | Content | | ------------------------------------------------------------ | ------------------------------------------------------------ | -| [Google Workspace](#google-workspace) | How to use Google Workspace as an SSO provider for OpenProject? | -| [Azure Active Directory](#azure-active-directory) | How to use Microsoft Azure Active Directory as an SSO provider for OpenProject? | -| [Custom OpenID Connect Providers](#custom-openid-connect-providers) | Configuration of additional OpenID Connect providers. | +| Login with [Google Workspace](#google) | How to use Google Workspace as an SSO provider for OpenProject? | +| [Microsoft Entra ID](#microsoft-entra) (previously Azure) | How to use Microsoft Azure Active Directory as an SSO provider for OpenProject? | +| [Custom OpenID Connect Providers](#custom-openid-connect-provider) | Configuration of additional OpenID Connect providers. | | [Troubleshooting](#troubleshooting) | Common complications when using OpenID as SSO. | To activate and configure OpenID providers in OpenProject, navigate to *Administration* -> *Authentication* and choose -> *OpenID providers*. -## Add a new authentication application for oauth +## Add a new OpenID Connect provider To add a new OpenID provider, click the green **+ OpenID provider** button. -![OpenID providers in OpenProject administration](openproject_system-admin-guide_authentication_openid_provider.png) +![OpenIDprovider selection in OpenProject administration](openproject_system-admin-guide_authentication_openid_provider_empty.png) -You can configure the following options. -1. Choose **Google** or **Azure** to add as an OpenID provider to OpenProject. -2. Optionally enter a **display name**. +You can create different kinds of providers with a different set of properties. You can choose from: -3. Enter the **Identifier**. +- [Google](#google) +- [Microsoft Entra ID](#microsoft-entra) (previously Azure) +- [Custom OpenID Connect Providers](#custom-openid-connect-provider) -4. Enter the **Secret**. -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. Set the **tenant** of your Azure endpoint. This will control who gets access to the OpenProject instance. For more information, please see [our user guide on Azure OpenID connect](#azure-active-directory) - -7. Press the **create** button. - - ![Add a new OpenID provider in OpenProject administration](openproject_system-admin-guide_authentication_openid_provider_new.png) - -## Google Workspace +## Google ### Step 1: Create the OAuth consent screen 1. Navigate to your GCP console. (https://console.cloud.google.com/) 2. Go to **APIs & Services** > OAuth consent screen. -![g1-apis-and-services-oauth-consent-screen](g1-apis-and-services-oauth-consent-screen.png) +![APIs and services OAuth consent screen](g1-apis-and-services-oauth-consent-screen.png) -3. Create a new project and a new app or edit an existing project and an existing app, setting the following fields (shall be Internal): +3. Create a new project and a new app or edit an existing project and an existing app, setting the following fields (should be internal): 1. **App name** (e.g. EXAMPLE.COM SSO) 2. **User support email** (e.g. user-support@example.com) 3. **App domains** (at minimum, you must provide the Application home page - e.g. `https://example.openproject.com`) @@ -58,7 +48,7 @@ You can configure the following options. 5. **Developer Contact information** (e.g. developer@example.com) 6. Click **SAVE AND CONTINUE** to proceed. -![g2-edit-app-registration](g2-edit-app-registration.png) +![Edit app registration](g2-edit-app-registration.png) 4. **Scopes** - Press **SAVE AND CONTINUE** 5. **Summary** - Press **SAVE AND CONTINUE** @@ -67,45 +57,45 @@ You can configure the following options. 1. Under **APIs & Services**, go to **Credentials**. -![g3-apis-and-services-credentials](g3-apis-and-services-credentials.png) +![APIs and services credentials](g3-apis-and-services-credentials.png) 2. Click **Create Credentials** and select **OAuth Client ID**. 1. When prompted for your **Application type**, choose **Web Application**. - 2. Provide a **Name** for your application. (e.g. example-openproject-com) + 2. Provide a **Name** for your application. (e.g. example-openproject.com) 3. Under Authorized redirect URIs, click **Add URI**, and provide your URI (e.g. [example.openproject.com]/auth/google/callback). 4. Click **CREATE** or **SAVE** . -![g4-create-credentials-oauth-client-id](g4-create-credentials-oauth-client-id.png) +![Create credentials for OAuth client id](g4-create-credentials-oauth-client-id.png) -After pressing **CREATE** you will get a pop-up window like the following +After pressing **CREATE** you will see a following pop-up window. -- Note **Client ID** -- Note **Client Secret** +> [!TIP] +> +> Make sure to note your **Client ID** and **Client Secret**. -![g5-oauth-client-created](g5-oauth-client-created.png) +![OAuth client created](g5-oauth-client-created.png) ### Step 3: Add Google as an OpenID Provider to OpenProject 1. Login as OpenProject Administrator -2. Navigate to *Administration* -> *Authentication* and choose -> *OpenID providers*. - 1. **Name** Choose Google - 2. **Display Name** (e.g. **EXAMPLE.COM SSO**) - 3. **Identifier** (**Client ID** from step 2) - 4. **Secret** (**Client Secret** from step 2) - 5. Enable **Limit self registration** option -3. Press **Create** +2. Navigate to *Administration* -> *Authentication* and choose -> *OpenID providers*. + - **Click** the green *+ OpenID Connect provider* button + - **Choose** Choose the Option Google + - Set a **Display Name**, this is the name of the login button shown to users. + - On the next section, set **Client ID** and **Client Secret** (from step 2) + - Enable **Limit self registration** option if you want users that create accounts with this provider to bypass the configured limit for self-registration. ![Add a new OpenID Gogole provider in OpenProject administration](openproject_system-admin-guide_authentication_openid_provider_new_google.png) -4. The following green notification **Successful creation** should appear +Press **Finish setup** to save the client and complete. If you go back to the index page of OpenID connect providers, the new provider should be visible. -![Successful OpenID creation message in OpenProject administration](openproject_system-admin-guide_authentication_openid_provider_new_google_successful_message.png) +![Saved Google authentication provider](openproject_system-admin-guide_authentication_openid_provider_saved_google.png) -## Azure Active Directory +## Microsoft Entra ### Step 1: Register an App in Azure Active Directory @@ -113,41 +103,51 @@ If your organization currently has an Azure Active Directory to manage users, an The steps are as follows: -Log into your Microsoft account, and go to the Azure Active Directory administration page. +1. Log into your Microsoft account, and go to the Azure Active Directory administration page. ![Azure Active Directory administration page](01-menu.png) -In the sidebar, click on "All services". +2. In the sidebar, click **All services**. ![Azure Active Directory All services](02-admin-dashboard.png) -Click on the link named "App registrations". +3. Click the link named **App registrations**. ![Azure Active Directory App registrations](03-app-registrations.png) -Click on "New registration". +4. Click **New registration**. ![Azure Active Directory New registration](04-register-app.png) -You are now asked for a few settings: +5. You will then be asked to specify the following settings: -* For "Name", enter "OpenProject". -* For "Supported account types", select "Accounts in this organization directory only". -* For "Redirect URI", select the "Web" type, and enter the URL to your OpenProject installation, followed by "/auth/azure/callback". For instance: "https://myserver.com/auth/azure/callback". +* For **Name**, enter *OpenProject*. +* For **Supported account types**, select *Accounts in this organization directory only*. +* For **Redirect URI**, select the *Web* type, and enter the URL to your OpenProject installation, followed by */auth/oidc-microsoft-entra/callback*. For instance: "https://myserver.com/auth/oidc-microsoft-entra/callback". -When you are done, click on the "Register" button at the end of the page. You are redirected to your new App registration, be sure to save the "Application (client) ID" that is now displayed. You will need it later. +> [!NOTE] +> +> The Redirect URI is dependent on the display name that you choose later on. You might need to change it to the correct value shown in the administration of OpenProject. + +6. When you are done, click the **Register** button at the end of the page. You will be redirected to your new App registration. + +> [!IMPORTANT] +> Make sure to save the **Application (client) ID** that is now displayed. You will need it later. ![Azure Active Directory Admin Dashboard](02-admin-dashboard-1580821056307.png) -You can now click on "Certificates & secret". +7. You can then click **Certificates & secret**. ![Azure Active Directory Certificates](06-certificates.png) -Then click on "New client secret", set the description to "client_secret", and the expiration to "730 days (24 months)". Then click on "Add". +8. Then click **New client secret**, set the description to *client_secret*, and the expiration to *730 days (24 months)*. Then click **Add**. ![Azure Active Directory New Client Secret](07-client-secret.png) -A secret should have been generated and is now displayed on the page. Be sure to save it somewhere because it will only be displayed once. +9. A secret should have been generated and will be displayed on the page. + +> [!IMPORTANT] +> Make sure to save it because it will only be displayed once. ![Azure Active Directory Add Secret](08-add-secret.png) @@ -155,30 +155,284 @@ At the end of this step, you should have a copy of the Application client ID as ### Step 2: Configure OpenProject -Now, head over to OpenProject > Administration > OpenID providers. Click on "New OpenID provider", select the Azure type, enter the client ID and client Secret. +Next, you need to create the OpenID Connect provider in OpenProject: + +1. Login as OpenProject Administrator +2. Navigate to *Administration* -> *Authentication* and choose -> *OpenID providers*. + - **Click** the green *+ OpenID Connect provider* button + - **Choose** Choose the option **Microsoft Entra** + - Set display name **Microsoft Entra**. Please note that if you change this value, the redirect URI in step 1) might change. The redirect URI is shown in the side panel on the right side once you saved the configuration. + - Set the **Tenant**: By default, OpenProject will use the Microsoft Graph API endpoint to perform user info requests. + For that, you will need to enter the correct tenant identifier for your Azure instance. + To find the correct value for your instance, [please see this guide](https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-protocols-oidc#find-your-apps-openid-configuration-document-uri). + - In the next section, set **Client ID** and **Client Secret** (from step 1) + - Enable **Limit self registration** option if you want users that create accounts with this provider to bypass the configured limit for self-registration. + +![Add a new OpenID Google provider in OpenProject administration](azure-display-name-tenant.png) + +Press **Finish setup** to save the client and complete. If you go back to the index page of OpenID connect providers, the new provider should be visible. There you will see the redirect URI on the right side in case you set a custom display name. + +![Saved Google authentication provider](./oidc-index-page.png) Congratulations, your users can now authenticate using your Microsoft Entra ID provider using the button in the Login form. + +## Custom OpenID Connect Provider + +Starting with OpenProject 15.0., you can also create custom OpenID Connect providers using the user interface. + +To start creating a custom provider, please follow these steps: + +1. Login as OpenProject Administrator +2. Navigate to *Administration* -> *Authentication* and choose -> *OpenID providers*. + - **Click** the green *+ OpenID Connect provider* button + - **Choose** Choose the *Option* **Custom** + +#### Step 1: Display name + +- Set a **Display Name**, this is the name of the login button shown to users. Let's assume we're trying to connect *Keycloak* with OpenProject for this example. We will type in Keycloak as that's the label of the button to be shown to users trying to authenticate. + + + +#### Step 2: Discovery endpoint + +- In the next section, you have the option to specify a discovery endpoint URL to pre-fill some public attributes + - For Keycloak, this URL is based on the configured realm name `http://keycloak.example.com:443/realms/{realm}/.well-known/openid-configuration` +- If you have a discovery endpoint URL, choose **I have a discovery endpoint URL** and fill it in +- Click **Continue**. With a discovery endpoint URL, OpenProject will try to fetch this information and take you to the next step. Observe the page for error responses in case it cannot connect to the endpoint or the returned information is invalid. + +![Discovery endpoint URL](./custom-provider-metadata-discovery.png) + +#### Step 3: Advanced configuration + +- Unless the metadata endpoint provided these values, you will have to fill out some required endpoint URLs, such as **Authorization endpoint**, **User information endpoint**, and **Token endpoint**. +- Fill out the **Issuer** field which depends on the provider. For Keycloak, this value would be the realm URL: `http://keycloak.example.com:443/realms/{realm}` +- Optionally fill out: + - **End session endpoint**, an URL where OpenProject should redirect to terminate a user's session. + - **JWKS URI**. This is the URL of the provider's JSON Web Key Set document containing e.g., signing keys and certificates. + - A custom icon by using a publicly available URL to fetch the logo from. +- Click **Continue** to validate this form and move to the next step. If there are any errors in this form, they will turn red and inform you about what you need to change. + +![Custom OpenID provider advanced configuration in OpenProject](custom-provider-advanced-config.png) + +#### Step 5: Client details + +In the next section, fill out the client credentials provided from your OpenID Connect provider: + +- Fill out **Client ID** and **Client secret** +- If you want users to be redirected to a separate endpoint _after logging out_ at the identity provider, set **Post Logout Redirect URI**. +- If you want this login mechanism to respect the global setting for self registration limits, check **Limit self registration**. +- Click **Continue**. + +#### Step 6: Optional attribute mapping + +You can optionally provide a custom mapping for attributes in the `userinfo` endpoint response. In most cases, you can leave this empty, unless you are providing custom attributes for user properties. + +If you need to set some of these values, enter the attribute key used/returned in the `userinfo` endpoint. + +For example: Keycloak allows you to map custom properties of the user. This allows you to specify a login with, e.g, `preferred_username` userinfo. In this case, you would fill out `Mapping for: Username` with that attribute returned in the userinfo JSON response. + +#### Step 7: Claims + +You can optionally request [claims](https://openid.net/specs/openid-connect-core-1_0-final.html#Claims) for both the id_token and userinfo endpoint. Keep in mind that currently only claims requested for the id_token returned with the authorize response are validated. That means that the authentication will fail if a requested essential claim is not returned. + +If you do not need Claims or are unaware of their use-cases, simply skip this step and click **Finish setup** . + +**Requesting MFA authentication via the ACR claim** + +Say for example that you want to request that the user authenticate using MFA (multi-factor authentication). You can do this by using the ACR (Authentication Context Class Reference) claim. + +This may look different for each identity provider. But if they follow, for instance the [EAP (Extended Authentication Profile)](https://openid.net/specs/openid-connect-eap-acr-values-1_0.html) then the claims would be `phr` (phishing-resistant) and ‘phrh’ (phishing-resistant hardware-protected). Others may simply have an additional claim called `Multi_Factor`. + +You have to check with your identity provider how these values must be called, as they vary from provider. + +In the following example we request a list of ACR values. One of which must be satisfied (i.e. returned in the ID token by the identity provider, meaning that the requested authentication mechanism was used) for the login in OpenProject to succeed. If none of the requested claims are present, authentication will fail. + +To specify these, you can provide a JSON. Use the following template as a starting point: + +``` +{ + "id_token": { + "acr": { + "essential": true, + "values": ["phr", "phrh", "Multi_Factor"] + } + } +} +``` + + + +**Non-essential claims** + +You may also request non-essential claims. In the example above this indicates that users should preferably be authenticated using those mechanisms but it’s not strictly required. The login into OpenProject will then work even if none of the claims are returned by the identity provider. + +**The acr_values option** + +For non-essential ACR claims you can also use the shorthand form of the option like this: + +``` +options = { ... } + +options["acr_values"] = "phr phrh Multi_Factor" +``` + +The option takes a space-separated list of ACR values. This is functionally the same as using the more complicated `claims` option above but with `"essential": false`. For all other claims there is no such shorthand. + +After entering Claims information, click **Finish setup** to complete the provider creation form. -By default, OpenProject will use the Microsoft Graph API endpoint to perform user info requests. -For that, you will need to enter the correct tenant identifier for your Azure instance. -To find the correct value for your instance, [please see this guide](https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-protocols-oidc#find-your-apps-openid-configuration-document-uri). +![Bildschirmfoto 2024-11-06 um 18.34.28](./custom-provider-claims.png) -Once you filled out the form, hit save and the Azure provider has been created. -You can now log out, and see that the login form displays a badge for authenticating with Azure. If you click on that badge, you will be redirected to Azure to enter your credentials and allow the App to access your Azure profile, and you should then be automatically logged in. -Congratulations, your users can now authenticate using your Azure Active Directory! +### Additional custom configuration instructions for Okta -#### Tenant configuration +If you use Okta with OpenID Connect, use these configuration properties in the custom provider form: -Sometimes you may need to configure the `tenant` option for the AzureAD connection. -Currently this is not possible through the user interface. +- **Display name:** Okta +- **Client ID / Secret**: Values provided from Okta +- **Authorization endpoint**: `/oauth2/v1/authorize` +- **User information endpoint**: `/oauth2/v1/userinfo` +- **Token endpoint**: `/oauth2/v1/token` +- **End session endpoint**: `https://mypersonal.okta.com/oauth2/{authorizationServerId}/v1/logout` -But you can do it via the console as described [here](../../../installation-and-operations/misc/custom-openid-connect-providers/#custom-openid-connect-providers) where you can add `tenant` next to the other options like `host`, `identifier` and `secret`. -## Custom OpenID Connect Providers -You can still use an arbitrary provider. But for the time being there is no user interface yet for this. That means you will have to do it directly using the console on the server or via environment variables. +### Additional custom configuration instructions for Keycloak + +In Keycloak, use the following steps to set up an OIDC integration for OpenProject: + +- Select or create a realm you want to authenticate OpenProject with. Remember that realm identifier. For the remainder of this section, we're using REALM as the placeholder you'll need to replace. +- Under **Clients** menu, click *Create* or *Create client* +- **Add client**: Enter the following details + - **Client type / protocol**: OpenID Connect + - **Client ID**: `https://` + - **Name**: Choose any name, used only within Keycloak +- For the **Capability config**, keep Standard flow checked. In our tested version of Keycloak, this was the default. +- Click **Save** + +You will be forwarded to the settings tab of the new client. Change these settings: + +- Set **Valid redirect URIs** to `https:///auth/oidc-keycloak/*` +- Enable **Sign Documents** +- If you want to enable [Backchannel logout](https://openid.net/specs/openid-connect-backchannel-1_0.html), set **Backchannel logout URL** to `https:///auth/oidc-keycloak/backchannel-logout` + +Next, you will need to create or note down the client secret for that client. + +- Go to the **Credentials** tab +- Click the **copy to clipboard button** next to **Client secret** to copy that value + +**OPTIONAL:** By default, OpenProject will map the user's email to the login attribute in OpenProject. If you want to change that, you can do it by providing an alternate claim value in Keycloak: + +- Go to **Client scopes** +- Click the `https://-dedicated` scope +- Click **Add mapper** and **By configuration** +- Select **User property** +- Assuming you want to provide the username as `preferred_username` to OpenProject, set these values. This will depend on what attribute you want to map: + - Set name to `username` + - Set Token claim name to `preferred_username` +- Click **Save** + + + +#### Form values for OpenProject + +In OpenProject, create a custom provider as shown above using these parameters + +- **Display name:** Keycloak +- **Client ID / Secret**: Credentials shown above +- **Authorization endpoint**: `/oauth2/v1/authorize` +- **User information endpoint**: `/oauth2/v1/userinfo` +- **Token endpoint**: `/oauth2/v1/token` +- **End session endpoint**: `https://mypersonal.okta.com/oauth2/{authorizationServerId}/v1/logout` +- **OpenProject Redirect URI**: `https://openproject.example.com/auth/oidc-keycloak/callback` (Note that this URL depends on the display name above. See the UI for the actual Redirect URI) + + + + + +## Configuration using environment variables + +For some deployment scenarios, it might be desirable to configure a provider through environment variables. + +> [!WARNING] +> Only do this if you know what you are doing. Otherwise this may break your existing OpenID Connect authentication or cause other issues. + +The provider entries are defined dynamically based on the environment keys. All variables will start with the prefix +`OPENPROJECT_OPENID__CONNECT_` followed by the provider name. For instance an Okta example would +be defined via environment variables like this: + +```shell +OPENPROJECT_OPENID__CONNECT_OKTA_DISPLAY__NAME="Okta" +OPENPROJECT_OPENID__CONNECT_OKTA_HOST="mypersonal.okta.com" +OPENPROJECT_OPENID__CONNECT_OKTA_IDENTIFIER="" +# etc. +``` + +Underscores in option names must be escaped by doubling them. So make sure to really do use two consecutive underscores in `DISPLAY__NAME`, `TOKEN__ENDPOINT` and so forth + +Use the following configuration as a template for your configuration. + +> [!NOTE] +> +> Replace `KEYCLOAK` in the environment name with an alphanumeric identifier. This will become the slug in the redirect URI like follows: +> +> `https://openproject.example.com/auth/keycloak/callback` +> +> You can also see the actual redirect URI in the user interface after the provider has been successfully created from these environment variables. + + + +```bash +# The name of the login button in OpenProject, you can freely set this to anything you like +OPENPROJECT_OPENID__CONNECT_KEYCLOAK_DISPLAY__NAME="Keycloak" + +# The Client ID of OpenProject, usually the client host in Keycloak +OPENPROJECT_OPENID__CONNECT_KEYCLOAK_IDENTIFIER="https://" + +# The Client Secret used by OpenProject for your provider +OPENPROJECT_OPENID__CONNECT_KEYCLOAK_SECRET="" + +# The Issuer configuration for your provider +OPENPROJECT_OPENID__CONNECT_KEYCLOAK_ISSUER="https://keycloak.example.com/realms/" + +# Endpoints for Authorization, Token, Userinfo +OPENPROJECT_OPENID__CONNECT_KEYCLOAK_AUTHORIZATION__ENDPOINT="/realms//protocol/openid-connect/auth" +OPENPROJECT_OPENID__CONNECT_KEYCLOAK_TOKEN__ENDPOINT="/realms//protocol/openid-connect/token" +OPENPROJECT_OPENID__CONNECT_KEYCLOAK_USERINFO__ENDPOINT="/realms//protocol/openid-connect/userinfo" + +# Optional: endpoint to redirect users for logout +OPENPROJECT_OPENID__CONNECT_KEYCLOAK_END__SESSION__ENDPOINT="http://keycloak.example.com/realms//protocol/openid-connect/logout" + +# Host name of Keycloak, required if endpoint information are not absolute URLs +OPENPROJECT_OPENID__CONNECT_KEYCLOAK_HOST="" + +# Optional: Specify if non-standard port +OPENPROJECT_OPENID__CONNECT_KEYCLOAK_PORT="443" + +# Optional: Specify if not using https (only for development/testing purposes) +OPENPROJECT_OPENID__CONNECT_KEYCLOAK_SCHEME="https" + +# Optional: Where to redirect the user after a completed logout flow +OPENPROJECT_OPENID__CONNECT_LOCALKEYCLOAK_POST__LOGOUT__REDIRECT__URI="http://example.com" + +# Optional: if you have created the client scope mapper as shown above +OPENPROJECT_OPENID__CONNECT_KEYCLOAK_ATTRIBUTE__MAP_LOGIN="preferred_username" + +# Optional: Claim mapping using acr_value syntax +OPENPROJECT_OPENID__CONNECT_KEYCLOAK_ACR__VALUES="phr phrh Multi_Factor" + +# Optional: Claim mapping using JSON, see Step 7 above for more information on syntax +OPENPROJECT_OPENID__CONNECT_KEYCLOAK_CLAIMS="{\"id_token\":{\"acr\":{\"essential\":true,\"values\":[\"phr\",\"phrh\",\"Multi_Factor\"]}}}" +``` + + + +### Applying the configuration + +To apply the configuration after changes, you need to run the `db:seed` rake task. In all installations, this command is run automatically when you upgrade or install your application. Use the following commands based on your installation method: + +- **Packaged installation**: `sudo openproject run bundle exec rake db:seed` +- **Docker**: `docker exec -it bundle exec rake db:seed`. + -Please continue reading in the [Miscellaneous section of the Installation and Operations Guide](../../../installation-and-operations/misc/custom-openid-connect-providers/). ## Troubleshooting @@ -186,22 +440,25 @@ Q: After clicking on a provider badge, I am redirected to a signup form that say A: This can happen if you previously created user accounts in OpenProject with the same email than what is stored in the OpenID provider. In this case, if you want to allow existing users to be automatically remapped to the OpenID provider, you should do the following: -Spawn an interactive console in OpenProject. The following example shows the command for the packaged installation. -See [our process control guide](../../../installation-and-operations/operation/control/) for information on other installation types. +Spawn an interactive console in OpenProject. The following example shows the command for the packaged installation. See [our process control guide](https://github.com/opf/openproject/blob/dev/docs/installation-and-operations/operation/control) for information on other installation types. -```shell +``` sudo openproject run console # or if using docker: # docker-compose run --rm web bundle exec rails console ``` + + Once in the console you can then enter the following to enable the setting and leave the console. -```shell +``` Setting.oauth_allow_remapping_of_existing_users = true exit ``` + + Then, existing users should be able to log in using their Azure identity. Note that this works only if the user is using password-based authentication, and is not linked to any other authentication source (e.g. LDAP) or OpenID provider. Note that this setting is set to true by default for new installations already. diff --git a/docs/system-admin-guide/authentication/openid-providers/azure-display-name-tenant.png b/docs/system-admin-guide/authentication/openid-providers/azure-display-name-tenant.png new file mode 100644 index 000000000000..9bbb7a5495db Binary files /dev/null and b/docs/system-admin-guide/authentication/openid-providers/azure-display-name-tenant.png differ diff --git a/docs/system-admin-guide/authentication/openid-providers/azure-provider-index.png b/docs/system-admin-guide/authentication/openid-providers/azure-provider-index.png new file mode 100644 index 000000000000..5908679a2964 Binary files /dev/null and b/docs/system-admin-guide/authentication/openid-providers/azure-provider-index.png differ diff --git a/docs/system-admin-guide/authentication/openid-providers/custom-provider-advanced-config.png b/docs/system-admin-guide/authentication/openid-providers/custom-provider-advanced-config.png new file mode 100644 index 000000000000..e7d477a353c9 Binary files /dev/null and b/docs/system-admin-guide/authentication/openid-providers/custom-provider-advanced-config.png differ diff --git a/docs/system-admin-guide/authentication/openid-providers/custom-provider-claims.png b/docs/system-admin-guide/authentication/openid-providers/custom-provider-claims.png new file mode 100644 index 000000000000..0178b49510b6 Binary files /dev/null and b/docs/system-admin-guide/authentication/openid-providers/custom-provider-claims.png differ diff --git a/docs/system-admin-guide/authentication/openid-providers/custom-provider-metadata-discovery.png b/docs/system-admin-guide/authentication/openid-providers/custom-provider-metadata-discovery.png new file mode 100644 index 000000000000..134426d9f556 Binary files /dev/null and b/docs/system-admin-guide/authentication/openid-providers/custom-provider-metadata-discovery.png differ diff --git a/docs/system-admin-guide/authentication/openid-providers/oidc-index-page.png b/docs/system-admin-guide/authentication/openid-providers/oidc-index-page.png new file mode 100644 index 000000000000..09c0e4432211 Binary files /dev/null and b/docs/system-admin-guide/authentication/openid-providers/oidc-index-page.png differ diff --git a/docs/system-admin-guide/authentication/openid-providers/openproject_system-admin-guide_authentication_openid_provider_empty.png b/docs/system-admin-guide/authentication/openid-providers/openproject_system-admin-guide_authentication_openid_provider_empty.png new file mode 100644 index 000000000000..57a9adaf5b34 Binary files /dev/null and b/docs/system-admin-guide/authentication/openid-providers/openproject_system-admin-guide_authentication_openid_provider_empty.png differ diff --git a/docs/system-admin-guide/authentication/openid-providers/openproject_system-admin-guide_authentication_openid_provider_new_google.png b/docs/system-admin-guide/authentication/openid-providers/openproject_system-admin-guide_authentication_openid_provider_new_google.png index 36301c3ff739..fece608b05a1 100644 Binary files a/docs/system-admin-guide/authentication/openid-providers/openproject_system-admin-guide_authentication_openid_provider_new_google.png and b/docs/system-admin-guide/authentication/openid-providers/openproject_system-admin-guide_authentication_openid_provider_new_google.png differ diff --git a/docs/system-admin-guide/authentication/openid-providers/openproject_system-admin-guide_authentication_openid_provider_new_google_successful_message.png b/docs/system-admin-guide/authentication/openid-providers/openproject_system-admin-guide_authentication_openid_provider_new_google_successful_message.png deleted file mode 100644 index 3ac418daf544..000000000000 Binary files a/docs/system-admin-guide/authentication/openid-providers/openproject_system-admin-guide_authentication_openid_provider_new_google_successful_message.png and /dev/null differ diff --git a/docs/system-admin-guide/authentication/openid-providers/openproject_system-admin-guide_authentication_openid_provider_saved_google.png b/docs/system-admin-guide/authentication/openid-providers/openproject_system-admin-guide_authentication_openid_provider_saved_google.png new file mode 100644 index 000000000000..9f2bb67a2a36 Binary files /dev/null and b/docs/system-admin-guide/authentication/openid-providers/openproject_system-admin-guide_authentication_openid_provider_saved_google.png differ diff --git a/docs/system-admin-guide/users-permissions/roles-permissions/README.md b/docs/system-admin-guide/users-permissions/roles-permissions/README.md index e18045341af9..8385f8a0f2ac 100644 --- a/docs/system-admin-guide/users-permissions/roles-permissions/README.md +++ b/docs/system-admin-guide/users-permissions/roles-permissions/README.md @@ -93,6 +93,17 @@ OpenProject allows to share project information with **anonymous** users which a | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | | Project-level: Permissions scoped to individual projects for users which are not logged in | - View work packages for users that are not logged in | Assign different permissions to the role *Anonymous* | +### Standard + +**Standard** is the default role of users of your OpenProject instance. It is configured by administrators on the instance level.
    + +>[!NOTE] +> The *Standard* role cannot be deleted and it is applied to every user on the instance. Users cannot be assigned to, or unassigned from this role. + +| Scope of the role | Permission examples | Customization options | +| ------------------------------------------------------------ | ---------------------------- | ---------------------------------------------------- | +| Application-level: Permissions scoped to specific administrative tasks (not restricted to specific projects) | - View user's mail addresses | Assign different permissions to the role *Anonymous* | + ## Customize roles with individual permissions Administrators can add new roles with custom permissions or configure existing ones in *Administration* > *Users and permissions* > *Roles and permissions*. diff --git a/docs/user-guide/home/global-modules/openproject_global_modules_activity.png b/docs/user-guide/home/global-modules/openproject_global_modules_activity.png index 2e32eeef5e35..45d61bc8301e 100644 Binary files a/docs/user-guide/home/global-modules/openproject_global_modules_activity.png and b/docs/user-guide/home/global-modules/openproject_global_modules_activity.png differ diff --git a/docs/user-guide/home/global-modules/openproject_global_modules_boards.png b/docs/user-guide/home/global-modules/openproject_global_modules_boards.png index f70ab7c268e9..e4667e645151 100644 Binary files a/docs/user-guide/home/global-modules/openproject_global_modules_boards.png and b/docs/user-guide/home/global-modules/openproject_global_modules_boards.png differ diff --git a/docs/user-guide/home/global-modules/openproject_global_modules_meetings.png b/docs/user-guide/home/global-modules/openproject_global_modules_meetings.png index 8f913a5ca6e8..7c99a435d15b 100644 Binary files a/docs/user-guide/home/global-modules/openproject_global_modules_meetings.png and b/docs/user-guide/home/global-modules/openproject_global_modules_meetings.png differ diff --git a/docs/user-guide/home/global-modules/openproject_global_modules_news.png b/docs/user-guide/home/global-modules/openproject_global_modules_news.png index 2a04765dec6c..7f1672693e83 100644 Binary files a/docs/user-guide/home/global-modules/openproject_global_modules_news.png and b/docs/user-guide/home/global-modules/openproject_global_modules_news.png differ diff --git a/docs/user-guide/home/global-modules/openproject_global_modules_team_planner.png b/docs/user-guide/home/global-modules/openproject_global_modules_team_planner.png index c6120e3787a2..4ab221bb60ea 100644 Binary files a/docs/user-guide/home/global-modules/openproject_global_modules_team_planner.png and b/docs/user-guide/home/global-modules/openproject_global_modules_team_planner.png differ diff --git a/docs/user-guide/home/global-modules/openproject_global_modules_time_costs.png b/docs/user-guide/home/global-modules/openproject_global_modules_time_costs.png index 01faf1001216..6d5a72576385 100644 Binary files a/docs/user-guide/home/global-modules/openproject_global_modules_time_costs.png and b/docs/user-guide/home/global-modules/openproject_global_modules_time_costs.png differ diff --git a/docs/user-guide/wiki/create-edit-wiki/openproject_user_guide_wiki_autosave_icon.png b/docs/user-guide/wiki/create-edit-wiki/openproject_user_guide_wiki_autosave_icon.png index 1818e4c714f5..50e2271c1e5a 100644 Binary files a/docs/user-guide/wiki/create-edit-wiki/openproject_user_guide_wiki_autosave_icon.png and b/docs/user-guide/wiki/create-edit-wiki/openproject_user_guide_wiki_autosave_icon.png differ diff --git a/docs/user-guide/work-packages/edit-work-package/openproject_user_guide_wp_autosave_icon.png b/docs/user-guide/work-packages/edit-work-package/openproject_user_guide_wp_autosave_icon.png index d6b69cd580b5..ed9623003c03 100644 Binary files a/docs/user-guide/work-packages/edit-work-package/openproject_user_guide_wp_autosave_icon.png and b/docs/user-guide/work-packages/edit-work-package/openproject_user_guide_wp_autosave_icon.png differ diff --git a/frontend/src/app/core/turbo/turbo-requests.service.ts b/frontend/src/app/core/turbo/turbo-requests.service.ts index f5dfb7364c0d..23bae83a6c90 100644 --- a/frontend/src/app/core/turbo/turbo-requests.service.ts +++ b/frontend/src/app/core/turbo/turbo-requests.service.ts @@ -10,24 +10,28 @@ export class TurboRequestsService { } - public request(url:string, init:RequestInit = {}):Promise { + public request(url:string, init:RequestInit = {}):Promise<{ html:string, headers:Headers }> { return fetch(url, init) .then((response) => { if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } - - return response.text(); + return response.text().then((html) => ({ + html, + headers: response.headers, + })); }) - .then((html) => { - renderStreamMessage(html); - - return html; // enable further processing wherever this is called - }) - .catch((error) => this.toast.addError(error as string)); + .then((result) => { + renderStreamMessage(result.html); + return result; + }) + .catch((error) => { + this.toast.addError(error as string); + throw error; + }); } - public requestStream(url:string):Promise { + public requestStream(url:string):Promise<{ html:string, headers:Headers }> { return this.request(url, { method: 'GET', headers: { Accept: 'text/vnd.turbo-stream.html' }, diff --git a/frontend/src/stimulus/controllers/dynamic/work-packages/activities-tab/index.controller.ts b/frontend/src/stimulus/controllers/dynamic/work-packages/activities-tab/index.controller.ts index 80c8305a3165..955d44d3d961 100644 --- a/frontend/src/stimulus/controllers/dynamic/work-packages/activities-tab/index.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/work-packages/activities-tab/index.controller.ts @@ -20,6 +20,7 @@ export default class IndexController extends Controller { userId: Number, workPackageId: Number, notificationCenterPathName: String, + lastServerTimestamp: String, }; static targets = ['journalsContainer', 'buttonRow', 'formRow', 'form', 'reactionButton']; @@ -32,7 +33,7 @@ export default class IndexController extends Controller { declare updateStreamsUrlValue:string; declare sortingValue:string; - declare lastUpdateTimestamp:string; + declare lastServerTimestampValue:string; declare intervallId:number; declare pollingIntervalInMsValue:number; declare notificationCenterPathNameValue:string; @@ -61,8 +62,8 @@ export default class IndexController extends Controller { this.turboRequests = context.services.turboRequests; this.apiV3Service = context.services.apiV3Service; + this.handleStemVisibility(); this.setLocalStorageKey(); - this.setLastUpdateTimestamp(); this.setupEventListeners(); this.handleInitialScroll(); this.startPolling(); @@ -166,8 +167,8 @@ export default class IndexController extends Controller { const journalsContainerAtBottom = this.isJournalsContainerScrolledToBottom(); void this.performUpdateStreamsRequest(this.prepareUpdateStreamsUrl()) - .then((html) => { - this.handleUpdateStreamsResponse(html as string, journalsContainerAtBottom); + .then(({ html, headers }) => { + this.handleUpdateStreamsResponse(html, headers, journalsContainerAtBottom); }).catch((error) => { console.error('Error updating activities list:', error); }).finally(() => { @@ -183,11 +184,11 @@ export default class IndexController extends Controller { const url = new URL(this.updateStreamsUrlValue); url.searchParams.set('sortBy', this.sortingValue); url.searchParams.set('filter', this.filterValue); - url.searchParams.set('last_update_timestamp', this.lastUpdateTimestamp); + url.searchParams.set('last_update_timestamp', this.lastServerTimestampValue); return url.toString(); } - private performUpdateStreamsRequest(url:string):Promise { + private performUpdateStreamsRequest(url:string):Promise<{ html:string, headers:Headers }> { return this.turboRequests.request(url, { method: 'GET', headers: { @@ -196,11 +197,14 @@ export default class IndexController extends Controller { }); } - private handleUpdateStreamsResponse(html:string, journalsContainerAtBottom:boolean) { - this.setLastUpdateTimestamp(); - this.checkForAndHandleWorkPackageUpdate(html); - this.checkForNewNotifications(html); - this.performAutoScrolling(html, journalsContainerAtBottom); + private handleUpdateStreamsResponse(html:string, headers:Headers, journalsContainerAtBottom:boolean) { + setTimeout(() => { + this.handleStemVisibility(); + this.setLastServerTimestampViaHeaders(headers); + this.checkForAndHandleWorkPackageUpdate(html); + this.checkForNewNotifications(html); + this.performAutoScrolling(html, journalsContainerAtBottom); + }, 100); } private checkForAndHandleWorkPackageUpdate(html:string) { @@ -426,6 +430,8 @@ export default class IndexController extends Controller { // scroll to (new) bottom if sorting is ascending and journals container was already at bottom before showing the form this.scrollJournalContainer(true); this.focusEditor(); + } else { + this.focusEditor(); } } @@ -515,8 +521,8 @@ export default class IndexController extends Controller { const formData = this.prepareFormData(); void this.submitForm(formData) - .then(() => { - this.handleSuccessfulSubmission(); + .then(({ html, headers }) => { + this.handleSuccessfulSubmission(html, headers); }) .catch((error) => { console.error('Error saving activity:', error); @@ -531,14 +537,14 @@ export default class IndexController extends Controller { const data = ckEditorInstance ? ckEditorInstance.getData({ trim: false }) : ''; const formData = new FormData(this.formTarget); - formData.append('last_update_timestamp', this.lastUpdateTimestamp); + formData.append('last_update_timestamp', this.lastServerTimestampValue); formData.append('filter', this.filterValue); formData.append('journal[notes]', data); return formData; } - private async submitForm(formData:FormData):Promise { + private async submitForm(formData:FormData):Promise<{ html:string, headers:Headers }> { return this.turboRequests.request(this.formTarget.action, { method: 'POST', body: formData, @@ -548,8 +554,9 @@ export default class IndexController extends Controller { }); } - private handleSuccessfulSubmission() { - this.setLastUpdateTimestamp(); + private handleSuccessfulSubmission(html:string, headers:Headers) { + // extract server timestamp from response headers in order to be in sync with the server + this.setLastServerTimestampViaHeaders(headers); if (!this.journalsContainerTarget) return; @@ -568,6 +575,7 @@ export default class IndexController extends Controller { true, ); } + this.handleStemVisibility(); }, 10); this.saveInProgress = false; @@ -588,7 +596,49 @@ export default class IndexController extends Controller { this.journalsContainerTarget.classList.add('work-packages-activities-tab-index-component--journals-container_with-input-compensation'); } - setLastUpdateTimestamp() { - this.lastUpdateTimestamp = new Date().toISOString(); + private setLastServerTimestampViaHeaders(headers:Headers) { + if (headers.has('X-Server-Timestamp')) { + this.lastServerTimestampValue = headers.get('X-Server-Timestamp') as string; + } + } + + // Towards the code below: + // Ideally the stem rendering would be correctly rendered for all UI states from the server + // but as we push single elements into the DOM via turbo-streams, the server-side rendered collection state gets stale quickly + // I've decided to go with a client-side rendering-correction approach for now + // as I don't want to introduce more complexity and queries (n+1 for position checks etc.) to the server-side rendering + private handleStemVisibility() { + this.handleStemVisibilityForMobile(); + this.handleLastStemPartVisibility(); + } + + private handleStemVisibilityForMobile() { + if (this.isMobile()) { + if (this.sortingValue === 'asc') return; + + const initialJournalContainer = this.element.querySelector('.work-packages-activities-tab-journals-item-component-details--journal-details-container[data-initial="true"]') as HTMLElement; + + if (initialJournalContainer) { + initialJournalContainer.classList.add('work-packages-activities-tab-journals-item-component-details--journal-details-container--border-removed'); + } + } + } + + private handleLastStemPartVisibility() { + const emptyLines = this.element.querySelectorAll('.empty-line'); + + // make sure all are visible first + emptyLines.forEach((container) => { + container.classList.remove('work-packages-activities-tab-journals-item-component-details--journal-details-container--hidden'); + }); + + if (this.sortingValue === 'asc' || this.filterValue === 'only_changes') return; + + // then hide the last one again + if (emptyLines.length > 0) { + // take the parent container of the last empty line + const lastEmptyLineContainer = emptyLines[emptyLines.length - 1].parentElement as HTMLElement; + lastEmptyLineContainer.classList.add('work-packages-activities-tab-journals-item-component-details--journal-details-container--hidden'); + } } } diff --git a/modules/openid_connect/app/components/openid_connect/providers/row_component.rb b/modules/openid_connect/app/components/openid_connect/providers/row_component.rb index 0eae5bfaabcf..c7e87dc5c26a 100644 --- a/modules/openid_connect/app/components/openid_connect/providers/row_component.rb +++ b/modules/openid_connect/app/components/openid_connect/providers/row_component.rb @@ -14,19 +14,24 @@ def column_args(column) end def name - link = render( + concat(provider_name) + unless provider.configured? + concat(incomplete_label) + end + end + + def provider_name + render( Primer::Beta::Link.new( - href: url_for(action: :edit, id: provider.id), + href: url_for(action: :show, id: provider.id), font_weight: :bold, mr: 1 ) ) { provider.display_name } - if !provider.configured? - link.concat( - render(Primer::Beta::Label.new(scheme: :attention)) { I18n.t(:label_incomplete) } - ) - end - link + end + + def incomplete_label + render(Primer::Beta::Label.new(scheme: :attention)) { I18n.t(:label_incomplete) } end def type diff --git a/modules/openid_connect/app/components/openid_connect/providers/sections/form_component.html.erb b/modules/openid_connect/app/components/openid_connect/providers/sections/form_component.html.erb index 5339d40ecdc1..677ed8c0b0fa 100644 --- a/modules/openid_connect/app/components/openid_connect/providers/sections/form_component.html.erb +++ b/modules/openid_connect/app/components/openid_connect/providers/sections/form_component.html.erb @@ -3,8 +3,7 @@ id: "openid-connect-providers-edit-form", model: provider, url:, - method: form_method, - data: { turbo: true, turbo_stream: true } + method: form_method ) do |form| flex_layout do |flex| if @heading diff --git a/modules/openid_connect/app/components/openid_connect/providers/side_panel/information_component.html.erb b/modules/openid_connect/app/components/openid_connect/providers/side_panel/information_component.html.erb new file mode 100644 index 000000000000..cc4a09471cab --- /dev/null +++ b/modules/openid_connect/app/components/openid_connect/providers/side_panel/information_component.html.erb @@ -0,0 +1,24 @@ +<%= + render(Primer::OpenProject::SidePanel::Section.new) do |section| + section.with_title { I18n.t("saml.providers.label_openproject_information") } + section.with_description { I18n.t("openid_connect.instructions.redirect_url") } + + component_collection do |collection| + collection.with_component(Primer::Beta::Heading.new(tag: :h5, mb: 1)) do + I18n.t("activemodel.attributes.openid_connect/provider.slug") + end + + collection.with_component( + OpPrimer::CopyToClipboardComponent.new(provider.slug, scheme: :input) + ) + + collection.with_component(Primer::Beta::Heading.new(tag: :h5, mt: 4, mb: 1)) do + I18n.t("activemodel.attributes.openid_connect/provider.redirect_url") + end + + collection.with_component( + OpPrimer::CopyToClipboardComponent.new(provider.callback_url, scheme: :input) + ) + end + end +%> diff --git a/modules/openid_connect/app/components/openid_connect/providers/side_panel/information_component.rb b/modules/openid_connect/app/components/openid_connect/providers/side_panel/information_component.rb new file mode 100644 index 000000000000..610e01b3e969 --- /dev/null +++ b/modules/openid_connect/app/components/openid_connect/providers/side_panel/information_component.rb @@ -0,0 +1,39 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 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 OpenIDConnect::Providers + module SidePanel + class InformationComponent < ApplicationComponent + include ApplicationHelper + include OpTurbo::Streamable + include OpPrimer::ComponentHelpers + + alias_method :provider, :model + end + end +end diff --git a/modules/openid_connect/app/components/openid_connect/providers/side_panel_component.html.erb b/modules/openid_connect/app/components/openid_connect/providers/side_panel_component.html.erb new file mode 100644 index 000000000000..b05772b94cb4 --- /dev/null +++ b/modules/openid_connect/app/components/openid_connect/providers/side_panel_component.html.erb @@ -0,0 +1,11 @@ +<%= + component_wrapper do + render(Primer::OpenProject::SidePanel.new(spacious: true)) do |panel| + [ + OpenIDConnect::Providers::SidePanel::InformationComponent.new(@provider), + ].each do |component| + panel.with_section(component) + end + end + end +%> diff --git a/modules/openid_connect/app/components/openid_connect/providers/side_panel_component.rb b/modules/openid_connect/app/components/openid_connect/providers/side_panel_component.rb new file mode 100644 index 000000000000..5764ea920d5a --- /dev/null +++ b/modules/openid_connect/app/components/openid_connect/providers/side_panel_component.rb @@ -0,0 +1,43 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 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 OpenIDConnect + module Providers + class SidePanelComponent < ApplicationComponent + include ApplicationHelper + include OpTurbo::Streamable + include OpPrimer::ComponentHelpers + + def initialize(provider) + super() + + @provider = provider + end + end + end +end diff --git a/modules/openid_connect/app/controllers/openid_connect/providers_controller.rb b/modules/openid_connect/app/controllers/openid_connect/providers_controller.rb index be964178961c..7e9cd03ca290 100644 --- a/modules/openid_connect/app/controllers/openid_connect/providers_controller.rb +++ b/modules/openid_connect/app/controllers/openid_connect/providers_controller.rb @@ -7,13 +7,15 @@ class ProvidersController < ::ApplicationController before_action :require_admin before_action :check_ee - before_action :find_provider, only: %i[edit update confirm_destroy destroy] + before_action :find_provider, only: %i[edit show update confirm_destroy destroy] before_action :set_edit_state, only: %i[create edit update] def index @providers = ::OpenIDConnect::Provider.all end + def show; end + def new oidc_provider = case params[:oidc_provider] when "google" @@ -44,7 +46,20 @@ def create end end - def edit; end + def edit + respond_to do |format| + format.turbo_stream do + component = OpenIDConnect::Providers::ViewComponent.new(@provider, + view_mode: :edit, + edit_mode: @edit_mode, + edit_state: @edit_state) + update_via_turbo_stream(component:) + scroll_into_view_via_turbo_stream("openid-connect-providers-edit-form", behavior: :instant) + render turbo_stream: turbo_streams + end + format.html + end + end def update update_params = params @@ -59,7 +74,7 @@ def update successful_save_response else @provider = call.result - failed_save_response(edit) + failed_save_response(:edit) end end diff --git a/modules/openid_connect/app/views/openid_connect/providers/edit.html.erb b/modules/openid_connect/app/views/openid_connect/providers/edit.html.erb index 186c079beb80..bc753198ad44 100644 --- a/modules/openid_connect/app/views/openid_connect/providers/edit.html.erb +++ b/modules/openid_connect/app/views/openid_connect/providers/edit.html.erb @@ -26,7 +26,21 @@ end %> -<%= render(OpenIDConnect::Providers::ViewComponent.new(@provider, - view_mode: :edit, - edit_mode: @edit_mode, - edit_state: @edit_state)) %> +<%= + render(Primer::Alpha::Layout.new(stacking_breakpoint: :md)) do |content| + content.with_main do + render(OpenIDConnect::Providers::ViewComponent.new(@provider, + view_mode: :edit, + edit_mode: @edit_mode, + edit_state: @edit_state)) + end + + if @provider.persisted? + content.with_sidebar(row_placement: :start, col_placement: :end) do + render(OpenIDConnect::Providers::SidePanelComponent.new(@provider)) + end + end + end +%> + +<%= %> diff --git a/modules/openid_connect/app/views/openid_connect/providers/show.html.erb b/modules/openid_connect/app/views/openid_connect/providers/show.html.erb new file mode 100644 index 000000000000..846ccd519fe6 --- /dev/null +++ b/modules/openid_connect/app/views/openid_connect/providers/show.html.erb @@ -0,0 +1,38 @@ +<% html_title(t(:label_administration), t('openid_connect.providers.plural'), @provider.display_name) -%> + +<% html_title(t(:label_administration), page_title) -%> + +<%= + render Primer::OpenProject::PageHeader.new do |header| + header.with_title { @provider.display_name } + header.with_breadcrumbs([{ href: admin_index_path, text: t(:label_administration) }, + { href: admin_settings_authentication_path, text: t(:label_authentication) }, + { href: openid_connect_providers_path, text: t("openid_connect.providers.plural") }, + @provider.display_name]) + header.with_action_button( + tag: :a, + scheme: :danger, + mobile_icon: :trash, + mobile_label: t(:button_delete), + size: :medium, + href: confirm_destroy_openid_connect_provider_path(@provider), + aria: { label: I18n.t(:button_delete) }, + title: I18n.t(:button_delete) + ) do |button| + button.with_leading_visual_icon(icon: :trash) + t(:button_delete) + end + end +%> + +<%= + render(Primer::Alpha::Layout.new(stacking_breakpoint: :md)) do |content| + content.with_main do + render OpenIDConnect::Providers::ViewComponent.new(@provider, view_mode: :show) + end + + content.with_sidebar(row_placement: :start, col_placement: :end) do + render OpenIDConnect::Providers::SidePanelComponent.new(@provider) + end + end +%> diff --git a/modules/openid_connect/config/locales/crowdin/af.yml b/modules/openid_connect/config/locales/crowdin/af.yml index 6f8a1dcd7830..57d7d7c75621 100644 --- a/modules/openid_connect/config/locales/crowdin/af.yml +++ b/modules/openid_connect/config/locales/crowdin/af.yml @@ -8,6 +8,7 @@ af: attributes: openid_connect/provider: name: Naam + slug: Unique identifier display_name: Display name client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ af: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ af: menu_title: OpenID providers delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/ar.yml b/modules/openid_connect/config/locales/crowdin/ar.yml index efe97ee38207..2f48c3d4b6ca 100644 --- a/modules/openid_connect/config/locales/crowdin/ar.yml +++ b/modules/openid_connect/config/locales/crowdin/ar.yml @@ -8,6 +8,7 @@ ar: attributes: openid_connect/provider: name: الاسم + slug: Unique identifier display_name: اسم العرض client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ ar: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ ar: menu_title: مزودو OpenID delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/az.yml b/modules/openid_connect/config/locales/crowdin/az.yml index e6b1ecd28ac3..c40ca1c1ab86 100644 --- a/modules/openid_connect/config/locales/crowdin/az.yml +++ b/modules/openid_connect/config/locales/crowdin/az.yml @@ -8,6 +8,7 @@ az: attributes: openid_connect/provider: name: Name + slug: Unique identifier display_name: Display name client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ az: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ az: menu_title: OpenID providers delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/be.yml b/modules/openid_connect/config/locales/crowdin/be.yml index 7b9949c80857..abc8e04f2b7d 100644 --- a/modules/openid_connect/config/locales/crowdin/be.yml +++ b/modules/openid_connect/config/locales/crowdin/be.yml @@ -8,6 +8,7 @@ be: attributes: openid_connect/provider: name: Імя + slug: Unique identifier display_name: Display name client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ be: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ be: menu_title: OpenID providers delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/bg.yml b/modules/openid_connect/config/locales/crowdin/bg.yml index a5509a6cb2b9..afd23659f307 100644 --- a/modules/openid_connect/config/locales/crowdin/bg.yml +++ b/modules/openid_connect/config/locales/crowdin/bg.yml @@ -8,6 +8,7 @@ bg: attributes: openid_connect/provider: name: Име + slug: Unique identifier display_name: Показвано име client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ bg: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ bg: menu_title: OpenID доставчици delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/ca.yml b/modules/openid_connect/config/locales/crowdin/ca.yml index 09001fd368ed..cbc8b7a06122 100644 --- a/modules/openid_connect/config/locales/crowdin/ca.yml +++ b/modules/openid_connect/config/locales/crowdin/ca.yml @@ -8,6 +8,7 @@ ca: attributes: openid_connect/provider: name: Nom + slug: Unique identifier display_name: Nom mostrat client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ ca: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ ca: menu_title: Proveïdor d’OpenID delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/ckb-IR.yml b/modules/openid_connect/config/locales/crowdin/ckb-IR.yml index 484935f7e556..c6a688bb60a9 100644 --- a/modules/openid_connect/config/locales/crowdin/ckb-IR.yml +++ b/modules/openid_connect/config/locales/crowdin/ckb-IR.yml @@ -8,6 +8,7 @@ ckb-IR: attributes: openid_connect/provider: name: Name + slug: Unique identifier display_name: Display name client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ ckb-IR: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ ckb-IR: menu_title: OpenID providers delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/cs.yml b/modules/openid_connect/config/locales/crowdin/cs.yml index 97e3ce38c342..b8eaa8e04f8f 100644 --- a/modules/openid_connect/config/locales/crowdin/cs.yml +++ b/modules/openid_connect/config/locales/crowdin/cs.yml @@ -8,6 +8,7 @@ cs: attributes: openid_connect/provider: name: Jméno + slug: Unique identifier display_name: Zobrazovaný název client_id: ID klienta client_secret: Client secret @@ -26,6 +27,7 @@ cs: icon: Vlastní ikona claims: Nároky acr_values: ACR hodnoty + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ cs: menu_title: Poskytovatelé OpenID delete_title: "Odstranit OpenID Connect poskytovatele" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: Tyto informace nemám metadata_url: Mám adresu URL koncového bodu zjišťování diff --git a/modules/openid_connect/config/locales/crowdin/da.yml b/modules/openid_connect/config/locales/crowdin/da.yml index 5b4f1024e94d..cb8ecd38cc5d 100644 --- a/modules/openid_connect/config/locales/crowdin/da.yml +++ b/modules/openid_connect/config/locales/crowdin/da.yml @@ -8,6 +8,7 @@ da: attributes: openid_connect/provider: name: Navn + slug: Unique identifier display_name: Display name client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ da: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ da: menu_title: OpenID providers delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/de.yml b/modules/openid_connect/config/locales/crowdin/de.yml index 1de3699bb13b..83996d5bc4bf 100644 --- a/modules/openid_connect/config/locales/crowdin/de.yml +++ b/modules/openid_connect/config/locales/crowdin/de.yml @@ -8,6 +8,7 @@ de: attributes: openid_connect/provider: name: Name + slug: Unique identifier display_name: Angezeigter Name client_id: Client-ID client_secret: Clientschlüssel @@ -26,6 +27,7 @@ de: icon: Benutzerdefiniertes Symbol claims: Claims acr_values: ACR-Werte + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ de: menu_title: OpenID-Anbieter delete_title: "OpenID Connect Anbieter löschen" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: Die Endpunkt-URL, die Sie vom OpenID Connect-Anbieter erhalten haben metadata_none: Ich habe diese Informationen nicht metadata_url: Ich habe eine Discovery-Endpunkt-URL diff --git a/modules/openid_connect/config/locales/crowdin/el.yml b/modules/openid_connect/config/locales/crowdin/el.yml index e0f2f0557ecd..99920f4895ac 100644 --- a/modules/openid_connect/config/locales/crowdin/el.yml +++ b/modules/openid_connect/config/locales/crowdin/el.yml @@ -8,6 +8,7 @@ el: attributes: openid_connect/provider: name: Όνομα + slug: Unique identifier display_name: Εμφανιζόμενο όνομα client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ el: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ el: menu_title: Πάροχοι OpenID delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/eo.yml b/modules/openid_connect/config/locales/crowdin/eo.yml index f825e17ff710..e9a17c160cb3 100644 --- a/modules/openid_connect/config/locales/crowdin/eo.yml +++ b/modules/openid_connect/config/locales/crowdin/eo.yml @@ -8,6 +8,7 @@ eo: attributes: openid_connect/provider: name: Nomo + slug: Unique identifier display_name: Display name client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ eo: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ eo: menu_title: OpenID providers delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/es.yml b/modules/openid_connect/config/locales/crowdin/es.yml index d98dfeb4fc78..8f63af7616b8 100644 --- a/modules/openid_connect/config/locales/crowdin/es.yml +++ b/modules/openid_connect/config/locales/crowdin/es.yml @@ -8,6 +8,7 @@ es: attributes: openid_connect/provider: name: Nombre + slug: Unique identifier display_name: Nombre para mostrar client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ es: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ es: menu_title: Proveedores de OpenID delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/et.yml b/modules/openid_connect/config/locales/crowdin/et.yml index b9d92ddcf61e..7c92849176c5 100644 --- a/modules/openid_connect/config/locales/crowdin/et.yml +++ b/modules/openid_connect/config/locales/crowdin/et.yml @@ -8,6 +8,7 @@ et: attributes: openid_connect/provider: name: Nimi + slug: Unique identifier display_name: Display name client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ et: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ et: menu_title: OpenID providers delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/eu.yml b/modules/openid_connect/config/locales/crowdin/eu.yml index a55051d7647e..e9aa246d3472 100644 --- a/modules/openid_connect/config/locales/crowdin/eu.yml +++ b/modules/openid_connect/config/locales/crowdin/eu.yml @@ -8,6 +8,7 @@ eu: attributes: openid_connect/provider: name: Name + slug: Unique identifier display_name: Display name client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ eu: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ eu: menu_title: OpenID providers delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/fa.yml b/modules/openid_connect/config/locales/crowdin/fa.yml index 590ffa7543ef..77d2d515c3b0 100644 --- a/modules/openid_connect/config/locales/crowdin/fa.yml +++ b/modules/openid_connect/config/locales/crowdin/fa.yml @@ -8,6 +8,7 @@ fa: attributes: openid_connect/provider: name: نام + slug: Unique identifier display_name: نمایش نام client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ fa: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ fa: menu_title: OpenID providers delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/fi.yml b/modules/openid_connect/config/locales/crowdin/fi.yml index d1ee4d675c46..5a55d81a41de 100644 --- a/modules/openid_connect/config/locales/crowdin/fi.yml +++ b/modules/openid_connect/config/locales/crowdin/fi.yml @@ -8,6 +8,7 @@ fi: attributes: openid_connect/provider: name: Nimi + slug: Unique identifier display_name: Display name client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ fi: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ fi: menu_title: OpenID providers delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/fil.yml b/modules/openid_connect/config/locales/crowdin/fil.yml index 66050659eaab..aeb7c557893e 100644 --- a/modules/openid_connect/config/locales/crowdin/fil.yml +++ b/modules/openid_connect/config/locales/crowdin/fil.yml @@ -8,6 +8,7 @@ fil: attributes: openid_connect/provider: name: Pangalan + slug: Unique identifier display_name: Display name client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ fil: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ fil: menu_title: OpenID providers delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/fr.yml b/modules/openid_connect/config/locales/crowdin/fr.yml index 575986b976a3..751cf3d10663 100644 --- a/modules/openid_connect/config/locales/crowdin/fr.yml +++ b/modules/openid_connect/config/locales/crowdin/fr.yml @@ -8,6 +8,7 @@ fr: attributes: openid_connect/provider: name: Nom + slug: Unique identifier display_name: Nom d'affichage client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ fr: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ fr: menu_title: Fournisseurs OpenID delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/he.yml b/modules/openid_connect/config/locales/crowdin/he.yml index d3ad9e927574..ac2975693fa5 100644 --- a/modules/openid_connect/config/locales/crowdin/he.yml +++ b/modules/openid_connect/config/locales/crowdin/he.yml @@ -8,6 +8,7 @@ he: attributes: openid_connect/provider: name: שם + slug: Unique identifier display_name: שם תצוגה client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ he: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ he: menu_title: OpenID providers delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/hi.yml b/modules/openid_connect/config/locales/crowdin/hi.yml index faabfb9ec4a5..d6d05fc47e65 100644 --- a/modules/openid_connect/config/locales/crowdin/hi.yml +++ b/modules/openid_connect/config/locales/crowdin/hi.yml @@ -8,6 +8,7 @@ hi: attributes: openid_connect/provider: name: नाम + slug: Unique identifier display_name: Display name client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ hi: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ hi: menu_title: OpenID providers delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/hr.yml b/modules/openid_connect/config/locales/crowdin/hr.yml index b37526042bd4..b1505a24936a 100644 --- a/modules/openid_connect/config/locales/crowdin/hr.yml +++ b/modules/openid_connect/config/locales/crowdin/hr.yml @@ -8,6 +8,7 @@ hr: attributes: openid_connect/provider: name: Naziv + slug: Unique identifier display_name: Display name client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ hr: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ hr: menu_title: OpenID providers delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/hu.yml b/modules/openid_connect/config/locales/crowdin/hu.yml index 78c9ee337fe1..9dd8c2430198 100644 --- a/modules/openid_connect/config/locales/crowdin/hu.yml +++ b/modules/openid_connect/config/locales/crowdin/hu.yml @@ -8,6 +8,7 @@ hu: attributes: openid_connect/provider: name: Név + slug: Unique identifier display_name: Megjelenített név client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ hu: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ hu: menu_title: OpenID szolgáltató delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/id.yml b/modules/openid_connect/config/locales/crowdin/id.yml index 2c7ff4522a49..6f4525fc1e67 100644 --- a/modules/openid_connect/config/locales/crowdin/id.yml +++ b/modules/openid_connect/config/locales/crowdin/id.yml @@ -8,6 +8,7 @@ id: attributes: openid_connect/provider: name: Nama + slug: Unique identifier display_name: Nama tampilan client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ id: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ id: menu_title: penyedia OpenID delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/it.yml b/modules/openid_connect/config/locales/crowdin/it.yml index da9eae4b88b5..593cc56ea5b1 100644 --- a/modules/openid_connect/config/locales/crowdin/it.yml +++ b/modules/openid_connect/config/locales/crowdin/it.yml @@ -8,6 +8,7 @@ it: attributes: openid_connect/provider: name: Nome + slug: Unique identifier display_name: Visualizza nome client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ it: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ it: menu_title: OpenID provider delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/ja.yml b/modules/openid_connect/config/locales/crowdin/ja.yml index aa28cce2d8f8..cbc00c4956c9 100644 --- a/modules/openid_connect/config/locales/crowdin/ja.yml +++ b/modules/openid_connect/config/locales/crowdin/ja.yml @@ -8,6 +8,7 @@ ja: attributes: openid_connect/provider: name: 名称 + slug: Unique identifier display_name: 表示名 client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ ja: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ ja: menu_title: OpenID プロバイダー delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/ka.yml b/modules/openid_connect/config/locales/crowdin/ka.yml index 018f091faf6c..49dbd9ea83d8 100644 --- a/modules/openid_connect/config/locales/crowdin/ka.yml +++ b/modules/openid_connect/config/locales/crowdin/ka.yml @@ -8,6 +8,7 @@ ka: attributes: openid_connect/provider: name: სახელი + slug: Unique identifier display_name: საჩვენებელი სახელი client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ ka: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ ka: menu_title: OpenID providers delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/kk.yml b/modules/openid_connect/config/locales/crowdin/kk.yml index 46351a97f3ad..2b7c36ec2092 100644 --- a/modules/openid_connect/config/locales/crowdin/kk.yml +++ b/modules/openid_connect/config/locales/crowdin/kk.yml @@ -8,6 +8,7 @@ kk: attributes: openid_connect/provider: name: Name + slug: Unique identifier display_name: Display name client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ kk: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ kk: menu_title: OpenID providers delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/ko.yml b/modules/openid_connect/config/locales/crowdin/ko.yml index 21b5a2dbbb5e..d436f194dd95 100644 --- a/modules/openid_connect/config/locales/crowdin/ko.yml +++ b/modules/openid_connect/config/locales/crowdin/ko.yml @@ -8,6 +8,7 @@ ko: attributes: openid_connect/provider: name: 이름 + slug: Unique identifier display_name: 표시 이름 client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ ko: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ ko: menu_title: OpenID 공급자 delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/lt.yml b/modules/openid_connect/config/locales/crowdin/lt.yml index 4e891d109c71..7d8a87dd8f0a 100644 --- a/modules/openid_connect/config/locales/crowdin/lt.yml +++ b/modules/openid_connect/config/locales/crowdin/lt.yml @@ -8,6 +8,7 @@ lt: attributes: openid_connect/provider: name: Vardas + slug: Unique identifier display_name: Rodomas vardas client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ lt: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ lt: menu_title: OpenID tiekėjai delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/lv.yml b/modules/openid_connect/config/locales/crowdin/lv.yml index 331549af2acb..d23e5a89a769 100644 --- a/modules/openid_connect/config/locales/crowdin/lv.yml +++ b/modules/openid_connect/config/locales/crowdin/lv.yml @@ -8,6 +8,7 @@ lv: attributes: openid_connect/provider: name: Nosaukums + slug: Unique identifier display_name: Display name client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ lv: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ lv: menu_title: OpenID providers delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/mn.yml b/modules/openid_connect/config/locales/crowdin/mn.yml index 72f29e9158a0..4912b38ffba8 100644 --- a/modules/openid_connect/config/locales/crowdin/mn.yml +++ b/modules/openid_connect/config/locales/crowdin/mn.yml @@ -8,6 +8,7 @@ mn: attributes: openid_connect/provider: name: Name + slug: Unique identifier display_name: Display name client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ mn: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ mn: menu_title: OpenID providers delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/ms.yml b/modules/openid_connect/config/locales/crowdin/ms.yml index 2f1d1760b099..f0ae18934d39 100644 --- a/modules/openid_connect/config/locales/crowdin/ms.yml +++ b/modules/openid_connect/config/locales/crowdin/ms.yml @@ -8,6 +8,7 @@ ms: attributes: openid_connect/provider: name: Nama + slug: Unique identifier display_name: Nama paparan client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ ms: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ ms: menu_title: Penyedia OpenID delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/ne.yml b/modules/openid_connect/config/locales/crowdin/ne.yml index 91f06509771e..0df3afb001e5 100644 --- a/modules/openid_connect/config/locales/crowdin/ne.yml +++ b/modules/openid_connect/config/locales/crowdin/ne.yml @@ -8,6 +8,7 @@ ne: attributes: openid_connect/provider: name: Name + slug: Unique identifier display_name: Display name client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ ne: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ ne: menu_title: OpenID providers delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/nl.yml b/modules/openid_connect/config/locales/crowdin/nl.yml index be7990cec3dd..e14645501c38 100644 --- a/modules/openid_connect/config/locales/crowdin/nl.yml +++ b/modules/openid_connect/config/locales/crowdin/nl.yml @@ -8,6 +8,7 @@ nl: attributes: openid_connect/provider: name: Naam + slug: Unique identifier display_name: Weergavenaam client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ nl: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ nl: menu_title: OpenID aanbieders delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/no.yml b/modules/openid_connect/config/locales/crowdin/no.yml index 4697c0885f38..4f99f39732c1 100644 --- a/modules/openid_connect/config/locales/crowdin/no.yml +++ b/modules/openid_connect/config/locales/crowdin/no.yml @@ -8,6 +8,7 @@ attributes: openid_connect/provider: name: Navn + slug: Unique identifier display_name: Visningsnavn client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ menu_title: OpenID-leverandører delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/pl.yml b/modules/openid_connect/config/locales/crowdin/pl.yml index f37f69b7a101..87ee3acdb56f 100644 --- a/modules/openid_connect/config/locales/crowdin/pl.yml +++ b/modules/openid_connect/config/locales/crowdin/pl.yml @@ -8,6 +8,7 @@ pl: attributes: openid_connect/provider: name: Nazwa + slug: Unique identifier display_name: Nazwa wyświetlana client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ pl: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ pl: menu_title: Dostawcy OpenID delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/pt-BR.yml b/modules/openid_connect/config/locales/crowdin/pt-BR.yml index d24a24f057bb..d00db9d3fa6b 100644 --- a/modules/openid_connect/config/locales/crowdin/pt-BR.yml +++ b/modules/openid_connect/config/locales/crowdin/pt-BR.yml @@ -8,6 +8,7 @@ pt-BR: attributes: openid_connect/provider: name: Nome + slug: Unique identifier display_name: Nome de exibição client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ pt-BR: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ pt-BR: menu_title: Provedores OpenID delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/pt-PT.yml b/modules/openid_connect/config/locales/crowdin/pt-PT.yml index 9f595c9f7961..669fcf76ab57 100644 --- a/modules/openid_connect/config/locales/crowdin/pt-PT.yml +++ b/modules/openid_connect/config/locales/crowdin/pt-PT.yml @@ -8,6 +8,7 @@ pt-PT: attributes: openid_connect/provider: name: Nome + slug: Unique identifier display_name: Nome a apresentar client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ pt-PT: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ pt-PT: menu_title: Fornecedores de OpenID delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/ro.yml b/modules/openid_connect/config/locales/crowdin/ro.yml index 6eb1ae5d9d83..f74f3ef2a08d 100644 --- a/modules/openid_connect/config/locales/crowdin/ro.yml +++ b/modules/openid_connect/config/locales/crowdin/ro.yml @@ -8,6 +8,7 @@ ro: attributes: openid_connect/provider: name: Nume + slug: Unique identifier display_name: Nume afișat client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ ro: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ ro: menu_title: Furnizori OpenID delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/ru.yml b/modules/openid_connect/config/locales/crowdin/ru.yml index 96fb67848d22..aac744ed42e4 100644 --- a/modules/openid_connect/config/locales/crowdin/ru.yml +++ b/modules/openid_connect/config/locales/crowdin/ru.yml @@ -8,6 +8,7 @@ ru: attributes: openid_connect/provider: name: Имя + slug: Unique identifier display_name: Отображаемое имя client_id: ID клиента client_secret: Закрытый ключ клиента @@ -26,6 +27,7 @@ ru: icon: Пользовательская иконка claims: Претензии acr_values: Значения ACR + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ ru: menu_title: Провайдеры OpenID delete_title: "Удалить провайдера OpenID Connect" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: У меня нет этой информации metadata_url: У меня есть URL конечной точки обнаружения diff --git a/modules/openid_connect/config/locales/crowdin/rw.yml b/modules/openid_connect/config/locales/crowdin/rw.yml index 4fba72f291c3..e0805990a366 100644 --- a/modules/openid_connect/config/locales/crowdin/rw.yml +++ b/modules/openid_connect/config/locales/crowdin/rw.yml @@ -8,6 +8,7 @@ rw: attributes: openid_connect/provider: name: Name + slug: Unique identifier display_name: Display name client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ rw: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ rw: menu_title: OpenID providers delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/si.yml b/modules/openid_connect/config/locales/crowdin/si.yml index 060dccf43db9..36b201aae74e 100644 --- a/modules/openid_connect/config/locales/crowdin/si.yml +++ b/modules/openid_connect/config/locales/crowdin/si.yml @@ -8,6 +8,7 @@ si: attributes: openid_connect/provider: name: නම + slug: Unique identifier display_name: Display name client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ si: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ si: menu_title: OpenID providers delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/sk.yml b/modules/openid_connect/config/locales/crowdin/sk.yml index 2d6b05a54341..ff2183aa9e01 100644 --- a/modules/openid_connect/config/locales/crowdin/sk.yml +++ b/modules/openid_connect/config/locales/crowdin/sk.yml @@ -8,6 +8,7 @@ sk: attributes: openid_connect/provider: name: Názov + slug: Unique identifier display_name: Display name client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ sk: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ sk: menu_title: OpenID providers delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/sl.yml b/modules/openid_connect/config/locales/crowdin/sl.yml index 77384fc3d2a5..6c5e965e24bb 100644 --- a/modules/openid_connect/config/locales/crowdin/sl.yml +++ b/modules/openid_connect/config/locales/crowdin/sl.yml @@ -8,6 +8,7 @@ sl: attributes: openid_connect/provider: name: Ime + slug: Unique identifier display_name: Prikazno ime client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ sl: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ sl: menu_title: OpenID ponudniki delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/sr.yml b/modules/openid_connect/config/locales/crowdin/sr.yml index 7785f0a08bf3..8ddb323d4ae6 100644 --- a/modules/openid_connect/config/locales/crowdin/sr.yml +++ b/modules/openid_connect/config/locales/crowdin/sr.yml @@ -8,6 +8,7 @@ sr: attributes: openid_connect/provider: name: Name + slug: Unique identifier display_name: Display name client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ sr: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ sr: menu_title: OpenID providers delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/sv.yml b/modules/openid_connect/config/locales/crowdin/sv.yml index 938683f71e49..17fca2880da2 100644 --- a/modules/openid_connect/config/locales/crowdin/sv.yml +++ b/modules/openid_connect/config/locales/crowdin/sv.yml @@ -8,6 +8,7 @@ sv: attributes: openid_connect/provider: name: Namn + slug: Unique identifier display_name: Visningsnamn client_id: Klient-ID client_secret: Client secret @@ -26,6 +27,7 @@ sv: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ sv: menu_title: OpenID leverantörer delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/th.yml b/modules/openid_connect/config/locales/crowdin/th.yml index 913bf06291c5..3445696c370d 100644 --- a/modules/openid_connect/config/locales/crowdin/th.yml +++ b/modules/openid_connect/config/locales/crowdin/th.yml @@ -8,6 +8,7 @@ th: attributes: openid_connect/provider: name: ชื่อ + slug: Unique identifier display_name: Display name client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ th: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ th: menu_title: OpenID providers delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/tr.yml b/modules/openid_connect/config/locales/crowdin/tr.yml index 7d9f7ded56d6..5e9e151b75c3 100644 --- a/modules/openid_connect/config/locales/crowdin/tr.yml +++ b/modules/openid_connect/config/locales/crowdin/tr.yml @@ -8,6 +8,7 @@ tr: attributes: openid_connect/provider: name: İsim + slug: Unique identifier display_name: Ekran adı client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ tr: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ tr: menu_title: OpenID sağlayıcıları delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/uk.yml b/modules/openid_connect/config/locales/crowdin/uk.yml index e0e189da27ba..4b07748b2a71 100644 --- a/modules/openid_connect/config/locales/crowdin/uk.yml +++ b/modules/openid_connect/config/locales/crowdin/uk.yml @@ -8,6 +8,7 @@ uk: attributes: openid_connect/provider: name: Ім’я + slug: Unique identifier display_name: Відображуване ім'я client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ uk: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ uk: menu_title: Постачальники OpenID delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: У мене немає цієї інформації metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/uz.yml b/modules/openid_connect/config/locales/crowdin/uz.yml index 5c2740a256c8..dbbc2a4b99aa 100644 --- a/modules/openid_connect/config/locales/crowdin/uz.yml +++ b/modules/openid_connect/config/locales/crowdin/uz.yml @@ -8,6 +8,7 @@ uz: attributes: openid_connect/provider: name: Name + slug: Unique identifier display_name: Display name client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ uz: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ uz: menu_title: OpenID providers delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/vi.yml b/modules/openid_connect/config/locales/crowdin/vi.yml index 0f241f32ff4d..545d5ab26fb1 100644 --- a/modules/openid_connect/config/locales/crowdin/vi.yml +++ b/modules/openid_connect/config/locales/crowdin/vi.yml @@ -8,6 +8,7 @@ vi: attributes: openid_connect/provider: name: Tên + slug: Unique identifier display_name: Tên hiển thị client_id: Client ID client_secret: Client secret @@ -26,6 +27,7 @@ vi: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ vi: menu_title: Các nhà cung cấp OpenID delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/locales/crowdin/zh-CN.yml b/modules/openid_connect/config/locales/crowdin/zh-CN.yml index 5965836bd7ec..e27cd16016e0 100644 --- a/modules/openid_connect/config/locales/crowdin/zh-CN.yml +++ b/modules/openid_connect/config/locales/crowdin/zh-CN.yml @@ -8,6 +8,7 @@ zh-CN: attributes: openid_connect/provider: name: 名称 + slug: Unique identifier display_name: 显示名称 client_id: 客户端 ID client_secret: 客户端密钥 @@ -26,6 +27,7 @@ zh-CN: icon: 自定义图标 claims: 声明 acr_values: ACR 值 + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ zh-CN: menu_title: OpenID 提供商 delete_title: "删除 OpenID Connect 提供商" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: OpenID Connect 提供商给您的端点 URL metadata_none: 我没有这个信息 metadata_url: 我有一个发现端点网址 diff --git a/modules/openid_connect/config/locales/crowdin/zh-TW.yml b/modules/openid_connect/config/locales/crowdin/zh-TW.yml index d2a8f938fd5b..c49495d13e1d 100644 --- a/modules/openid_connect/config/locales/crowdin/zh-TW.yml +++ b/modules/openid_connect/config/locales/crowdin/zh-TW.yml @@ -8,6 +8,7 @@ zh-TW: attributes: openid_connect/provider: name: 名稱 + slug: Unique identifier display_name: 顯示名稱 client_id: 客戶端 ID client_secret: 用戶端密鑰 @@ -26,6 +27,7 @@ zh-TW: icon: 自訂圖示 claims: 要求 acr_values: ACR 值 + redirect_url: Redirect URL activerecord: errors: models: @@ -51,6 +53,7 @@ zh-TW: menu_title: OpenID 提供商 delete_title: "刪除 OpenID Connect 提供者" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: OpenID Connect 提供者給予您的端點 URL metadata_none: 我沒有這些資訊 metadata_url: 我有一個發現端點 URL diff --git a/modules/openid_connect/config/locales/en.yml b/modules/openid_connect/config/locales/en.yml index 76689750d8c5..d6bafddb14f1 100644 --- a/modules/openid_connect/config/locales/en.yml +++ b/modules/openid_connect/config/locales/en.yml @@ -9,6 +9,7 @@ en: attributes: openid_connect/provider: name: Name + slug: Unique identifier display_name: Display name client_id: Client ID client_secret: Client secret @@ -27,6 +28,7 @@ en: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -55,6 +57,7 @@ en: delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/routes.rb b/modules/openid_connect/config/routes.rb index aa31fbcf26f3..110a5b553d6a 100644 --- a/modules/openid_connect/config/routes.rb +++ b/modules/openid_connect/config/routes.rb @@ -3,7 +3,7 @@ scope :admin do namespace :openid_connect do - resources :providers, except: %i[show] do + resources :providers do get :confirm_destroy, on: :member end end diff --git a/spec/features/activities/work_package/activities_spec.rb b/spec/features/activities/work_package/activities_spec.rb index e5968834e576..b8ced544dd82 100644 --- a/spec/features/activities/work_package/activities_spec.rb +++ b/spec/features/activities/work_package/activities_spec.rb @@ -540,6 +540,30 @@ end end + describe "focus editor" do + current_user { admin } + let(:work_package) { create(:work_package, project:, author: admin) } + + before do + wp_page.visit! + wp_page.wait_for_activity_tab + end + + it "focuses the editor", :aggregate_failures do + activity_tab.set_journal_sorting(:desc) + + activity_tab.open_new_comment_editor + + activity_tab.expect_focus_on_editor + + activity_tab.set_journal_sorting(:asc) + + activity_tab.open_new_comment_editor + + activity_tab.expect_focus_on_editor + end + end + describe "sorting" do current_user { admin } let(:work_package) { create(:work_package, project:, author: admin) } diff --git a/spec/support/components/work_packages/activities.rb b/spec/support/components/work_packages/activities.rb index d7870c48bb64..9d8694adaf27 100644 --- a/spec/support/components/work_packages/activities.rb +++ b/spec/support/components/work_packages/activities.rb @@ -144,11 +144,19 @@ def expect_no_input_field expect(page).not_to have_test_selector("op-work-package-journal-form") end - def add_comment(text: nil, save: true) - sleep 1 # otherwise the stimulus component is not mounted yet and the click does not work + def open_new_comment_editor + page.find_test_selector("op-open-work-package-journal-form-trigger").click + end + + def expect_focus_on_editor + page.within_test_selector("op-work-package-journal-form-element") do + expect(page).to have_css(".ck-content:focus") + end + end + def add_comment(text: nil, save: true) if page.find_test_selector("op-open-work-package-journal-form-trigger") - page.find_test_selector("op-open-work-package-journal-form-trigger").click + open_new_comment_editor else expect(page).to have_test_selector("op-work-package-journal-form-element") end @@ -184,8 +192,6 @@ def edit_comment(journal, text: nil) end def quote_comment(journal) - sleep 1 # otherwise the stimulus component is not mounted yet and the click does not work - within_journal_entry(journal) do page.find_test_selector("op-wp-journal-#{journal.id}-action-menu").click page.find_test_selector("op-wp-journal-#{journal.id}-quote").click