Add UI for SCIM Clients

Allowing to manage SCIM clients through the UI, including
all the authentication methods and their related "behind the scenes"
setup, i.e. service account, oauth application and access tokens.
This commit is contained in:
Jan Sandbrink
2025-06-13 16:25:00 +02:00
parent c263d9d794
commit f87b3ee347
49 changed files with 2179 additions and 149 deletions
@@ -0,0 +1,45 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) 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.
++#%>
<%=
render(Primer::OpenProject::InputGroup.new(input_width: :large)) do |input_group|
input_group.with_text_input(
name: :client_id,
label: Doorkeeper::Application.human_attribute_name(:uid),
visually_hide_label: false,
value: model.oauth_application.uid
)
input_group.with_trailing_action_clipboard_copy_button(
value: model.oauth_application.uid,
aria: {
label: I18n.t("button_copy_to_clipboard")
}
)
end
%>
@@ -0,0 +1,36 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 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 Admin
module ScimClients
class ClientIdComponent < ApplicationComponent
end
end
end
@@ -0,0 +1,72 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) 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.
++#%>
<%= render(Primer::OpenProject::FeedbackDialog.new(title: t(".title"), size: :large, classes: "Overlay--size-large-portrait", **system_arguments)) do |dialog|
dialog.with_feedback_message(icon_arguments: { icon: :"check-circle" }) do |message|
message.with_heading(tag: :h2).with_content(t(".heading"))
end
dialog.with_additional_details(display: :block) do
render(Primer::OpenProject::FlexLayout.new) do |layout|
layout.with_row do
render(Primer::OpenProject::InputGroup.new) do |input_group|
input_group.with_text_input(
name: :client_id,
label: Doorkeeper::Application.human_attribute_name(:uid),
visually_hide_label: false,
value: model.oauth_application.uid
)
input_group.with_trailing_action_clipboard_copy_button(
value: model.oauth_application.uid,
aria: { label: I18n.t("button_copy_to_clipboard") }
)
end
end
layout.with_row do
render(Primer::OpenProject::InputGroup.new) do |input_group|
input_group.with_text_input(
name: :client_secret,
label: Doorkeeper::Application.human_attribute_name(:secret),
visually_hide_label: false,
value: model.oauth_application.secret
)
input_group.with_trailing_action_clipboard_copy_button(
value: model.oauth_application.secret,
aria: { label: I18n.t("button_copy_to_clipboard") }
)
end
end
layout.with_row(mt: 3) do
render(Primer::Alpha::Banner.new(scheme: :warning, icon: :alert)) { t(".one_time_hint") }
end
end
end
end %>
@@ -0,0 +1,43 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 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 Admin
module ScimClients
class CreatedClientCredentialsDialogComponent < ApplicationComponent
include OpTurbo::Streamable
TEST_SELECTOR = "op-scim-clients--created-client-credentials-dialog"
def system_arguments
options
end
end
end
end
@@ -0,0 +1,53 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) 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.
++#%>
<%= render(Primer::OpenProject::FeedbackDialog.new(title: t(".title"), size: :large, **system_arguments)) do |dialog|
dialog.with_feedback_message(icon_arguments: { icon: :"check-circle" }) do |message|
message.with_heading(tag: :h2).with_content(t(".heading"))
end
dialog.with_additional_details(display: :block) do
concat(
render(Primer::OpenProject::InputGroup.new) do |input_group|
input_group.with_text_input(
name: :token,
label: t(".label_token"),
visually_hide_label: false,
value: model.token
)
input_group.with_trailing_action_clipboard_copy_button(
value: model.token,
aria: { label: I18n.t("button_copy_to_clipboard") }
)
end
)
concat(render(Primer::Alpha::Banner.new(scheme: :warning, icon: :alert, mt: 3)) { t(".one_time_hint") })
end
end %>
@@ -0,0 +1,43 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 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 Admin
module ScimClients
class CreatedTokenDialogComponent < ApplicationComponent
include OpTurbo::Streamable
TEST_SELECTOR = "op-scim-clients--created-static-token-dialog"
def system_arguments
options
end
end
end
end
@@ -0,0 +1,43 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) 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.
++#%>
<%=
render(
Primer::OpenProject::DangerDialog.new(
title: t(".title"),
form_arguments:,
test_selector: TEST_SELECTOR
)
) do |dialog|
dialog.with_confirmation_message do |message|
message.with_heading(tag: :h2) { t(".heading") }
message.with_description_content(t(".description"))
end
end
%>
@@ -0,0 +1,46 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 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 Admin
module ScimClients
class DeleteScimClientDialogComponent < ApplicationComponent
include OpTurbo::Streamable
TEST_SELECTOR = "op-scim-clients--delete-client-dialog"
def form_arguments
{
action: admin_scim_client_path(model),
method: :delete
}
end
end
end
end
@@ -0,0 +1,36 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) 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.
++#%>
<%=
component_wrapper(tag: "turbo-frame") do
settings_primer_form_with(**form_options) do |f|
render(ScimClients::Form.new(f))
end
end
%>
@@ -0,0 +1,63 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 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 Admin::ScimClients
class FormComponent < ApplicationComponent
include ApplicationHelper
include OpPrimer::ComponentHelpers
include OpTurbo::Streamable
def self.wrapper_key = :scim_clients_form
private
def form_options
form_target.merge(stimulus_controller_options)
.merge(model:)
end
def form_target
if model.new_record?
{ method: :post, url: admin_scim_clients_path }
else
{ method: :patch, url: admin_scim_client_path(model) }
end
end
def stimulus_controller_options
{
data: {
controller: "scim-clients--form-inputs",
turbo_frame: "_top"
}
}
end
end
end
@@ -0,0 +1,44 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) 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.
++#%>
<%=
render(
Primer::OpenProject::DangerDialog.new(
title: t(".title"),
confirm_button_text: t(".confirm_button"),
form_arguments:,
test_selector: TEST_SELECTOR
)
) do |dialog|
dialog.with_confirmation_message do |message|
message.with_heading(tag: :h2) { t(".heading") }
message.with_description_content(t(".description"))
end
end
%>
@@ -28,28 +28,31 @@
# See COPYRIGHT and LICENSE files for more details.
#++
module ScimClients
FormModel = Data.define(:name, :auth_provider_id, :authentication_method, :jwt_sub) do
extend ActiveModel::Naming
module Admin
module ScimClients
class RevokeStaticTokenDialogComponent < ApplicationComponent
include OpTurbo::Streamable
class << self
def from_client(client)
jwt_sub = client.service_account&.active_user_auth_provider_link&.external_id
new(
name: client.name,
auth_provider_id: client.auth_provider_id,
authentication_method: client.authentication_method,
jwt_sub:
)
TEST_SELECTOR = "op-scim-clients--revoke-static-token-dialog"
def initialize(model, scim_client_id:, turbo_frame: nil)
super(model)
@scim_client_id = scim_client_id
@turbo_frame = turbo_frame
end
def from_params(params)
new(
name: params[:name],
auth_provider_id: params[:auth_provider_id],
authentication_method: params[:authentication_method].to_s,
jwt_sub: params[:jwt_sub]
)
def form_arguments
{
action: admin_scim_client_static_token_path(model, scim_client_id: @scim_client_id),
method: :delete
}.merge(turbo_frame_arguments)
end
def turbo_frame_arguments
return {} if @turbo_frame.nil?
{ data: { turbo_frame: @turbo_frame } }
end
end
end
@@ -0,0 +1,51 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 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 Admin::ScimClients
class RowComponent < OpPrimer::BorderBoxRowComponent
def name
render(Primer::Beta::Link.new(href: edit_admin_scim_client_path(model), font_weight: :bold)) { model.name }
end
def user_count
return "" if model.auth_provider.nil?
model.auth_provider.users.count
end
def authentication_method
t("admin.scim_clients.authentication_methods.#{model.authentication_method}")
end
def created_at
helpers.format_date(model.created_at)
end
end
end
@@ -0,0 +1,65 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 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 Admin::ScimClients
class TableComponent < OpPrimer::BorderBoxTableComponent
columns :name, :user_count, :authentication_method, :created_at
mobile_labels :user_count, :authentication_method, :created_at
def mobile_title
ScimClient.model_name.human(count: 2)
end
def row_class
RowComponent
end
def headers
[
[:name, { caption: ScimClient.human_attribute_name(:name) }],
[:user_count, { caption: t(".user_count") }],
[:authentication_method, { caption: ScimClient.human_attribute_name(:authentication_method) }],
[:created_at, { caption: ScimClient.human_attribute_name(:created_at) }]
]
end
def blank_title
t(".blank_slate.title")
end
def blank_description
t(".blank_slate.description")
end
def blank_icon
:key
end
end
end
@@ -0,0 +1,52 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) 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.
++#%>
<%= component_wrapper(tag: "turbo-frame") do %>
<%= render(Primer::Beta::Subhead.new(spacious: true)) do |component|
component.with_heading { t(".heading") }
component.with_description { t(".description") }
end
%>
<%= render(Admin::ScimClients::TokenTableComponent.new(rows: access_tokens)) %>
<%= primer_form_with(url: admin_scim_client_static_tokens_path(model), method: :post) do %>
<%= render(
Primer::Beta::Button.new(
mt: 3,
type: :submit,
"aria-label": t(".label_aria_add_token"),
test_selector: "op-scim-clients--add-token-button"
)
) do |button|
button.with_leading_visual_icon(icon: :plus)
t(".label_add_token")
end %>
<% end %>
<% end %>
@@ -0,0 +1,43 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 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 Admin
module ScimClients
class TokenListComponent < ApplicationComponent
include OpTurbo::Streamable
def self.wrapper_key = :scim_clients_token_list
def access_tokens
model.access_tokens.order(created_at: :desc)
end
end
end
end
@@ -0,0 +1,78 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 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 Admin::ScimClients
class TokenRowComponent < OpPrimer::BorderBoxRowComponent
def created_at
format_date(model.created_at)
end
def expires_at
if model.revoked?
t("admin.scim_clients.token_table_component.revoked", date: format_date(model.revoked_at))
elsif model.expired?
t("admin.scim_clients.token_table_component.expired", date: format_date(model.expires_at))
else
format_date(model.expires_at)
end
end
def button_links
[revoke_button] # invisible button outside of menu
end
def revoke_button
return if model.revoked? || model.expired?
render(
Primer::Beta::IconButton.new(
scheme: :invisible,
"aria-label": t("button_revoke"),
icon: :"no-entry",
tag: :a,
href: deletion_dialog_admin_scim_client_static_token_path(model, scim_client_id: scim_client.id,
target: TokenListComponent.wrapper_key),
data: { controller: "async-dialog" },
test_selector: "op-scim-clients--revoke-token-button"
)
)
end
private
def scim_client
model.application.integration
end
def format_date(date)
helpers.format_date(date)
end
end
end
@@ -0,0 +1,63 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 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 Admin::ScimClients
class TokenTableComponent < OpPrimer::BorderBoxTableComponent
columns :created_at, :expires_at
mobile_labels :created_at, :expires_at
def mobile_title
t(".title")
end
def row_class
TokenRowComponent
end
def headers
[
[:created_at, { caption: Doorkeeper::AccessToken.human_attribute_name(:created_at) }],
[:expires_at, { caption: Doorkeeper::AccessToken.human_attribute_name(:expires_at) }]
]
end
def blank_title
t(".blank_slate.title")
end
def blank_description
t(".blank_slate.description")
end
def has_actions?
true
end
end
end
+12 -1
View File
@@ -29,6 +29,17 @@
#++
module ScimClients
class CreateContract < BaseContract
class CreateContract < ModelContract
attribute :name
validates :name, presence: true
attribute :auth_provider
validates :auth_provider, presence: true
attribute :authentication_method
validates :authentication_method, inclusion: { in: ScimClient.authentication_methods.keys }
attribute :jwt_sub
validates :jwt_sub, presence: true, if: -> { @model.authentication_method_sso? }
end
end
@@ -29,6 +29,7 @@
#++
module ScimClients
class UpdateContract < BaseContract
class UpdateContract < CreateContract
attribute :authentication_method, writable: false
end
end
@@ -0,0 +1,63 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 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 Admin
class ScimClientStaticTokensController < ::ApplicationController
include OpTurbo::ComponentStream
before_action :require_admin
def create
scim_client = ScimClient.find(params[:scim_client_id])
result = ::ScimClients::GenerateStaticTokenService.new(scim_client).call
update_via_turbo_stream(component: Admin::ScimClients::TokenListComponent.new(scim_client))
respond_with_dialog ScimClients::CreatedTokenDialogComponent.new(result.result)
end
def deletion_dialog
respond_with_dialog ScimClients::RevokeStaticTokenDialogComponent.new(
Doorkeeper::AccessToken.find(params[:id]),
scim_client_id: params[:scim_client_id],
turbo_frame: params[:target].presence
)
end
def destroy
token = Doorkeeper::AccessToken.find(params[:id])
scim_client = ScimClient.find(params[:scim_client_id])
::ScimClients::RevokeStaticTokenService.new(scim_client).call(token)
redirect_to edit_admin_scim_client_path(scim_client)
end
end
end
@@ -0,0 +1,132 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 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 Admin
class ScimClientsController < ::ApplicationController
include OpTurbo::ComponentStream
before_action :require_admin
menu_item :scim_clients
layout "admin"
def index
@scim_clients = ScimClient.order(:name)
end
def new
@scim_client = ScimClient.new(authentication_method: :oauth2_token)
end
def edit
@scim_client = ScimClient.find(params[:id])
first_time_setup(@scim_client)
end
def create
result = ::ScimClients::CreateService.new(user: User.current).call(scim_client_params)
result.on_failure do
@scim_client = result.result
stream_form_component do |format|
format.html { render :new }
end
end
result.on_success do
flash[:notice] = t(:notice_successful_create)
redirect_to edit_admin_scim_client_path(result.result, first_time_setup: true)
end
end
def update
@scim_client = ScimClient.find(params[:id])
result = ::ScimClients::UpdateService.new(user: User.current, model: @scim_client).call(scim_client_params)
result.on_failure do
stream_form_component do |format|
format.html { render :edit }
end
end
result.on_success do
flash[:notice] = t(:notice_successful_update)
redirect_to action: :index
end
end
def deletion_dialog
respond_with_dialog ScimClients::DeleteScimClientDialogComponent.new(ScimClient.find(params[:id]))
end
def destroy
model = ScimClient.find(params[:id])
result = ::ScimClients::DeleteService.new(user: User.current, model:).call
if result.success?
flash[:notice] = I18n.t(:notice_successful_delete)
else
flash[:error] = result.errors.full_messages
end
redirect_to action: :index
end
private
def scim_client_params
params.expect(scim_client: %i[name auth_provider_id authentication_method jwt_sub])
end
def first_time_setup(scim_client)
return if params[:first_time_setup].blank?
case scim_client.authentication_method
when "oauth2_token"
if scim_client.access_tokens.empty?
::ScimClients::GenerateStaticTokenService.new(scim_client).call
@setup_token = true
end
when "oauth2_client"
# Ensuring that the client secret can't infinitely be accessed by calling with ?first_time_setup=true long after
# the initial setup (there is no other persisted marker showing us, that this is the first time)
if scim_client.oauth_application.created_at > 1.minute.ago
@setup_client_credentials = true
end
end
end
def stream_form_component(&)
update_via_turbo_stream(component: Admin::ScimClients::FormComponent.new(@scim_client))
respond_with_turbo_streams(&)
end
end
end
+105
View File
@@ -0,0 +1,105 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 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 ScimClients
class Form < ApplicationForm
form do |client_form|
client_form.text_field(
name: :name,
label: ScimClient.human_attribute_name(:name),
required: true,
caption: I18n.t("admin.scim_clients.form.name_description"),
input_width: :large
)
client_form.select_list(
name: :auth_provider_id,
label: ScimClient.human_attribute_name(:auth_provider_id),
caption: I18n.t("admin.scim_clients.form.auth_provider_description"),
input_width: :large,
include_blank: false
) do |select|
AuthProvider.find_each do |provider|
select.option(
value: provider.id,
label: provider.display_name
)
end
end
client_form.select_list(
name: :authentication_method,
label: ScimClient.human_attribute_name(:authentication_method),
caption: I18n.t("admin.scim_clients.form.authentication_method_description_html").html_safe,
input_width: :large,
include_blank: false,
disabled: model.persisted?,
data: {
action: "scim-clients--form-inputs#updateFormInputs",
"scim-clients--form-inputs-target": "authenticationMethodInput"
}
) do |select|
ScimClient.authentication_methods.each_key do |method|
select.option(
value: method,
label: I18n.t("admin.scim_clients.authentication_methods.#{method}")
)
end
end
client_form.group(data: { "scim-clients--form-inputs-target": "jwtSubInputWrapper" }) do |group|
group.text_field(
name: :jwt_sub,
label: ScimClient.human_attribute_name(:jwt_sub),
required: true,
caption: I18n.t("admin.scim_clients.form.jwt_sub_description_html", docs_url: "#").html_safe, # TODO: correct docs url
input_width: :large
)
end
if show_client_id?
client_form.html_content do
render(Admin::ScimClients::ClientIdComponent.new(model))
end
end
client_form.submit(
name: :submit,
label: model.persisted? ? I18n.t(:button_save) : I18n.t(:button_create),
scheme: :primary,
data: { "scim-clients--form-inputs-target": "submitButton" }
)
end
def show_client_id?
model.persisted? && model.authentication_method_oauth2_client?
end
end
end
+19
View File
@@ -41,4 +41,23 @@ class ScimClient < ApplicationRecord
oauth2_client: 1,
oauth2_token: 2
}, scopes: false, prefix: true
def access_tokens
return Doorkeeper::AccessToken.none unless authentication_method_oauth2_token?
oauth_application.access_tokens
end
def jwt_sub
auth_provider_link&.external_id
end
# This method is part of a nasty workaround for creating and updating SCIM clients:
# To be able to validate the jwt_sub, the SetAttributesService must be able to effectively set the jwt_sub,
# before it's validated by a contract. Afterwards the UpdateService must be able to persist the change. Since
# user_auth_provider_links is a has_many association, there is no built-in memoization for values. So to make sure the
# SetAttributesService, the Contract and the UpdateService all look at the same jwt_sub, we memoize the auth_provider_link here
def auth_provider_link
@auth_provider_link ||= service_account&.user_auth_provider_links&.first
end
end
+2 -13
View File
@@ -33,24 +33,12 @@ class ScimClients::CreateService < BaseServices::Create
super.tap do |service_result|
self.model = service_result.result
update_service_account
update_oauth_application(service_result)
end
end
private
def update_service_account
service_account.name = params[:name]
if model.authentication_method_sso?
service_account.user_auth_provider_links.build(
auth_provider_id: params[:auth_provider_id],
external_id: params[:jwt_sub]
)
end
service_account.save!
end
def update_oauth_application(service_result)
return if !model.authentication_method_oauth2_client? && !model.authentication_method_oauth2_token?
@@ -60,7 +48,7 @@ class ScimClients::CreateService < BaseServices::Create
end
def service_account
@service_account ||= model.build_service_account(admin: true)
model.service_account
end
def create_oauth_application
@@ -69,6 +57,7 @@ class ScimClients::CreateService < BaseServices::Create
.call(
name: "#{model.name} (#{ScimClient.model_name.human})",
redirect_uri: "urn:ietf:wg:oauth:2.0:oob",
client_credentials_user_id: service_account.id,
scopes: "scim_v2",
confidential: true,
integration: model,
+1 -1
View File
@@ -29,7 +29,7 @@
#++
class ScimClients::DeleteService < BaseServices::Delete
def before_perform(_, call)
def before_perform(call)
# pre-loading service_account association before destroy to ensure it's available afterwards
call.result.service_account
call
@@ -0,0 +1,54 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 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.
#++
class ScimClients::GenerateStaticTokenService < BaseServices::BaseCallable
def initialize(scim_client)
super()
@scim_client = scim_client
end
def perform
return ServiceResult.failure unless @scim_client.authentication_method_oauth2_token?
token = @scim_client.oauth_application.access_tokens.create(scopes: "scim_v2", expires_in:)
if token.persisted?
ServiceResult.success(result: token)
else
ServiceResult.failure(errors: token.errors)
end
end
private
def expires_in
(1.year.from_now - Time.zone.now).to_i
end
end
@@ -0,0 +1,45 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 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.
#++
class ScimClients::RevokeStaticTokenService < BaseServices::BaseCallable
def initialize(scim_client)
super()
@scim_client = scim_client
end
def perform(access_token)
return ServiceResult.failure if access_token.application.integration != @scim_client
access_token.revoke unless access_token.revoked?
ServiceResult.success(result: access_token)
end
end
@@ -34,6 +34,24 @@ module ScimClients
def set_attributes(params)
super(params.except(:jwt_sub))
update_service_account
end
def update_service_account
service_account.assign_attributes(params.slice(:name))
if model.authentication_method_sso?
auth_provider_link.assign_attributes(params.slice(:auth_provider_id))
auth_provider_link.external_id = params[:jwt_sub] if params.key?(:jwt_sub)
end
end
def service_account
model.service_account || model.build_service_account(admin: true)
end
def auth_provider_link
@auth_provider_link ||= model.auth_provider_link || service_account.user_auth_provider_links.build
end
end
end
+3 -20
View File
@@ -31,26 +31,9 @@
class ScimClients::UpdateService < BaseServices::Update
def after_perform(_)
super.tap do |result|
update_service_account(result.result)
scim_client = result.result
scim_client.service_account&.save!
scim_client.auth_provider_link&.save!
end
end
private
def update_service_account(scim_client)
scim_client.service_account&.update!(params.slice(:name))
if model.authentication_method_sso?
link = scim_client.service_account&.user_auth_provider_links&.find_or_initialize_by({})
update_user_auth_provider_link(link)
end
end
def update_user_auth_provider_link(link)
return if link.nil?
link.auth_provider_id = params[:auth_provider_id] if params.key?(:auth_provider_id)
link.external_id = params[:jwt_sub] if params.key?(:jwt_sub)
link.save!
end
end
@@ -0,0 +1,66 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) 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.
++#%>
<% html_title t(:label_administration), ScimClient.model_name.human(count: 2), @scim_client.name %>
<%=
render(Primer::OpenProject::PageHeader.new) do |header|
header.with_title { @scim_client.name }
header.with_description { t("admin.scim_clients.form.description_html", docs_url: "#") } # TODO: Deep link into documentation
header.with_breadcrumbs(
[{ href: admin_index_path, text: t(:label_administration) },
{ href: admin_settings_authentication_path, text: t(:label_authentication) },
{ href: admin_scim_clients_path, text: ScimClient.model_name.human(count: 2) },
@scim_client.name]
)
header.with_action_button(
scheme: :danger,
mobile_icon: :trash,
mobile_label: t("button_delete"),
aria: { label: t(".label_delete_scim_client") },
tag: :a,
href: deletion_dialog_admin_scim_client_path(@scim_client),
data: { controller: "async-dialog" }
) do |button|
button.with_leading_visual_icon(icon: :trash)
t("button_delete")
end
end
%>
<%= render(Admin::ScimClients::FormComponent.new(@scim_client)) %>
<%= render(Admin::ScimClients::TokenListComponent.new(@scim_client)) if @scim_client.authentication_method_oauth2_token? %>
<% if @setup_token %>
<%= render(Admin::ScimClients::CreatedTokenDialogComponent.new(@scim_client.access_tokens.first, data: { controller: "auto-show-dialog" })) %>
<% elsif @setup_client_credentials %>
<%= render(Admin::ScimClients::CreatedClientCredentialsDialogComponent.new(@scim_client, data: { controller: "auto-show-dialog" })) %>
<% end %>
@@ -0,0 +1,55 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) 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.
++#%>
<% html_title t(:label_administration), ScimClient.model_name.human(count: 2) %>
<%=
render(Primer::OpenProject::PageHeader.new) do |header|
header.with_title { ScimClient.model_name.human(count: 2) }
header.with_description { t(".description") }
header.with_breadcrumbs(
[{ href: admin_index_path, text: t(:label_administration) },
{ href: admin_settings_authentication_path, text: t(:label_authentication) },
ScimClient.model_name.human(count: 2)]
)
end
%>
<%=
render(Primer::OpenProject::SubHeader.new) do |component|
component.with_action_button(leading_icon: :plus,
label: t(".label_create_button"),
scheme: :primary,
tag: :a,
href: new_admin_scim_client_path) { ScimClient.model_name.human }
end
%>
<%= render(Admin::ScimClients::TableComponent.new(rows: @scim_clients)) %>
+45
View File
@@ -0,0 +1,45 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) 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.
++#%>
<% html_title t(:label_administration), ScimClient.model_name.human(count: 2), t(".title") %>
<%=
render(Primer::OpenProject::PageHeader.new) do |header|
header.with_title { t(".title") }
header.with_description { t("admin.scim_clients.form.description_html", docs_url: "#") } # TODO: Deep link into documentation
header.with_breadcrumbs(
[{ href: admin_index_path, text: t(:label_administration) },
{ href: admin_settings_authentication_path, text: t(:label_authentication) },
{ href: admin_scim_clients_path, text: ScimClient.model_name.human(count: 2) },
t(".title")]
)
end
%>
<%= render(Admin::ScimClients::FormComponent.new(@scim_client)) %>
+3
View File
@@ -49,3 +49,6 @@ OpenProject::FeatureDecisions.add :calculated_value_project_attribute,
OpenProject::FeatureDecisions.add :block_note_editor,
description: "Enables the block note editor for rich text fields where available."
OpenProject::FeatureDecisions.add :scim_api,
description: "Enables SCIM API."
+13 -8
View File
@@ -510,14 +510,6 @@ Redmine::MenuManager.map :admin_menu do |menu|
caption: :"authentication.login_and_registration",
parent: :authentication
menu.push :ldap_authentication,
{ controller: "/ldap_auth_sources", action: "index" },
if: ->(_) { User.current.admin? && !OpenProject::Configuration.disable_password_login? },
parent: :authentication,
caption: :label_ldap_auth_source_plural,
html: { class: "server_authentication" },
last: true
menu.push :oauth_applications,
{ controller: "/oauth/applications", action: "index" },
if: ->(_) { User.current.admin? },
@@ -525,6 +517,19 @@ Redmine::MenuManager.map :admin_menu do |menu|
caption: :"oauth.application.plural",
html: { class: "oauth_applications" }
menu.push :ldap_authentication,
{ controller: "/ldap_auth_sources", action: "index" },
if: ->(_) { User.current.admin? && !OpenProject::Configuration.disable_password_login? },
parent: :authentication,
caption: :label_ldap_auth_source_plural,
html: { class: "server_authentication" }
menu.push :scim_clients,
{ controller: "/admin/scim_clients", action: "index" },
if: ->(_) { User.current.admin? && OpenProject::FeatureDecisions.scim_api_active? },
parent: :authentication,
caption: ScimClient.model_name.human(count: 2)
menu.push :announcements,
{ controller: "/announcements", action: "edit" },
if: ->(_) { User.current.admin? },
+60 -3
View File
@@ -119,6 +119,59 @@ en:
explanation:
text: "Individual actions of a user (e.g. updating a work package twice) are aggregated into a single action if their age difference is less than the specified timespan. They will be displayed as a single action within the application. This will also delay notifications by the same amount of time reducing the number of emails being sent and will also affect %{webhook_link} delay."
link: "webhook"
scim_clients:
authentication_methods:
sso: "JWT from identity provider"
oauth2_client: "OAuth 2.0 client credentials"
oauth2_token: "Static access token"
created_client_credentials_dialog_component:
title: "Client credentials created"
heading: "Client credentials have been generated"
one_time_hint: "This is the only time you will see the client secret. Make sure to copy it now."
created_token_dialog_component:
title: "Token created"
heading: "An token has been generated"
label_token: "Token"
one_time_hint: "This is the only time you will see this token. Make sure to copy it now."
delete_scim_client_dialog_component:
title: "Delete SCIM client"
heading: "Are you sure you want to delete this SCIM client?"
description: "Users managed by this SCIM client can no longer be updated by it."
edit:
label_delete_scim_client: "Delete SCIM client"
form:
auth_provider_description: "This is the service that users added by the SCIM provider will use to authenticate in OpenProject."
authentication_method_description_html: "This is how the SCIM client authenticates at OpenProject. Please ensure that OAuth tokens include the <code>scim_v2</code> scope."
description_html: 'Please refer to our <a href="%{docs_url}">documentation on configuring SCIM clients</a> for more information on these configuration options.'
jwt_sub_description_html: 'For example, for Keycloak, this is the UUID of the service account associated with the SCIM client. Consult <a href="%{docs_url}">our documentation</a> to learn how to find the subject claim for your use case.'
name_description: "Choose a name that will help other admins better understand why this client was configured."
index:
description: "SCIM clients are able to interact with OpenProject SCIM server API to provision, update, and deprovision user accounts and groups."
label_create_button: "Add SCIM client"
new:
title: "New SCIM client"
revoke_static_token_dialog_component:
confirm_button: "Revoke"
title: "Revoke static token"
heading: "Are you sure you want to revoke this token?"
description: "The SCIM client that uses this token will no longer be able to access OpenProject's SCIM server API."
table_component:
blank_slate:
title: "No SCIM clients configured yet"
description: "Add clients to see them here"
user_count: "Users"
token_list_component:
description: "The tokens you generate here can be passed by a SCIM client to access the OpenProject SCIM API."
heading: "Tokens"
label_add_token: "Token"
label_aria_add_token: "Add token"
token_table_component:
blank_slate:
title: "No tokens have been created yet"
description: "You can create one now"
expired: "Expired on %{date}"
revoked: "Revoked on %{date}"
title: "Access token table"
authentication:
login_and_registration: "Login and registration"
@@ -966,7 +1019,7 @@ en:
attribute_name: "Attribute"
help_text: "Help text"
auth_provider:
scim_clients: "SCIM Clients"
scim_clients: "SCIM clients"
capability:
context: "Context"
changeset:
@@ -1130,6 +1183,10 @@ en:
url: "URL"
role:
permissions: "Permissions"
scim_client:
auth_provider: "Authentication provider"
authentication_method: "Authentication method"
jwt_sub: "Subject claim"
status:
is_closed: "Work package closed"
is_readonly: "Work package read-only"
@@ -1660,8 +1717,8 @@ en:
one: "Role"
other: "Roles"
scim_client:
one: "SCIM Client"
other: "SCIM Clients"
one: "SCIM client"
other: "SCIM clients"
status: "Work package status"
token/api:
one: Access token
+12
View File
@@ -630,6 +630,18 @@ Rails.application.routes.draw do
controller: "/admin/attachments/quarantined_attachments",
only: %i[index destroy]
resources :scim_clients, only: %i[index edit new create update destroy] do
member do
get :deletion_dialog
end
resources :static_tokens, only: %i[create destroy], controller: "/admin/scim_client_static_tokens" do
member do
get :deletion_dialog
end
end
end
resource :backups, controller: "/admin/backups", only: %i[show] do
collection do
get :reset_token
@@ -0,0 +1,63 @@
/*
* -- copyright
* OpenProject is an open source project management software.
* Copyright (C) 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.
* ++
*/
import { Controller } from '@hotwired/stimulus';
export default class FormInputsController extends Controller {
static targets = [
'jwtSubInputWrapper',
'authenticationMethodInput',
'submitButton',
];
declare readonly jwtSubInputWrapperTarget:HTMLDivElement;
declare readonly authenticationMethodInputTarget:HTMLInputElement;
declare readonly submitButtonTarget:HTMLButtonElement;
connect() {
this.updateFormInputs();
}
updateFormInputs() {
if (this.authenticationMethodInputTarget.value === 'sso') {
this.showJwtSubInput();
} else {
this.hideJwtSubInput();
}
}
showJwtSubInput() {
this.jwtSubInputWrapperTarget.classList.remove('d-none');
}
hideJwtSubInput() {
this.jwtSubInputWrapperTarget.classList.add('d-none');
}
}
@@ -27,6 +27,7 @@ module OpenProject
:plugin_saml,
:saml_providers_path,
parent: :authentication,
after: :oauth_applications,
caption: ->(*) { I18n.t("saml.menu_title") },
enterprise_feature: "sso_auth_providers"
end
@@ -14,7 +14,7 @@ module OpenProject::LdapGroups
:plugin_ldap_groups,
{ controller: "/ldap_groups/synchronized_groups", action: :index },
parent: :authentication,
last: true,
after: :ldap_authentication,
caption: ->(*) { I18n.t("ldap_groups.label_menu_item") },
enterprise_feature: "ldap_groups"
end
@@ -45,6 +45,7 @@ module OpenProject::OpenIDConnect
:plugin_openid_connect,
:openid_connect_providers_path,
parent: :authentication,
after: :oauth_applications,
caption: ->(*) { I18n.t("openid_connect.menu_title") },
enterprise_feature: "sso_auth_providers"
end
+10
View File
@@ -33,5 +33,15 @@ FactoryBot.define do
sequence(:name) { |n| "SCIM Client #{n}" }
auth_provider factory: :oidc_provider
authentication_method { :sso }
trait :oauth2_token do
authentication_method { :oauth2_token }
oauth_application
end
trait :oauth2_client do
authentication_method { :oauth2_client }
oauth_application
end
end
end
@@ -0,0 +1,103 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
require "spec_helper"
RSpec.describe "Creating a SCIM client", :js, :selenium, driver: :firefox_de do
shared_let(:admin) { create(:admin, preferences: { time_zone: "Etc/UTC" }) }
shared_let(:oidc_provider) { create(:oidc_provider) }
current_user { admin }
it "can create a SCIM client authenticating through JWT", :aggregate_failures do
visit new_admin_scim_client_path
expect(page).to be_axe_clean.within("#content")
.skipping("link-in-text-block") # https://community.openproject.org/wp/65252
expect(page).to have_no_field("Subject claim")
select "JWT from identity provider", from: "Authentication method"
expect(page).to have_field("Subject claim")
select oidc_provider.display_name, from: "Authentication provider"
click_on("Create") # forgot to fill out form
expect(page).to have_text("Name can't be blank")
expect(page).to have_text("Subject claim can't be blank")
fill_in "Name", with: "My SCIM Client"
fill_in "Subject claim", with: "123-abc-456-def"
click_on("Create")
wait_for { ScimClient.find_by(name: "My SCIM Client") }.not_to be_nil
created_client = ScimClient.find_by(name: "My SCIM Client")
expect(page).to have_current_path(edit_admin_scim_client_path(created_client, first_time_setup: true))
expect(created_client.auth_provider_id).to eq(oidc_provider.id)
expect(created_client.authentication_method).to eq("sso")
expect(created_client.auth_provider_link&.external_id).to eq("123-abc-456-def")
end
it "can create a SCIM client authenticating through client credentials" do
visit new_admin_scim_client_path
fill_in "Name", with: "My SCIM Client"
select oidc_provider.display_name, from: "Authentication provider"
select "OAuth 2.0 client credentials", from: "Authentication method"
click_on("Create")
wait_for { ScimClient.find_by(name: "My SCIM Client") }.not_to be_nil
created_client = ScimClient.find_by(name: "My SCIM Client")
expect(page).to have_current_path(edit_admin_scim_client_path(created_client, first_time_setup: true))
page.within_modal("Client credentials created") do
expect(page).to have_field("Client ID", with: created_client.oauth_application.uid)
expect(page).to have_field("Client secret", with: created_client.oauth_application.secret)
end
end
it "can create a SCIM client authenticating through a static access token" do
visit new_admin_scim_client_path
fill_in "Name", with: "My SCIM Client"
select oidc_provider.display_name, from: "Authentication provider"
select "Static access token", from: "Authentication method"
click_on("Create")
wait_for { ScimClient.find_by(name: "My SCIM Client") }.not_to be_nil
created_client = ScimClient.find_by(name: "My SCIM Client")
expect(page).to have_current_path(edit_admin_scim_client_path(created_client, first_time_setup: true))
page.within_modal("Token created") do
expect(page).to have_field("Token", with: created_client.oauth_application.access_tokens.last.token)
end
end
end
@@ -0,0 +1,75 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
require "spec_helper"
RSpec.describe "Listing SCIM clients", :js, :selenium, driver: :firefox_de do
shared_let(:admin) { create(:admin, preferences: { time_zone: "Etc/UTC" }) }
current_user { admin }
context "when there are no SCIM clients" do
it "renders a proper blank slate" do
visit admin_scim_clients_path
expect(page).to be_axe_clean.within "#content"
within_test_selector("Admin::ScimClients::TableComponent") do
expect(page).to have_content("No SCIM clients configured yet")
expect(page).to have_content("Add clients to see them here")
end
within(".SubHeader") do
click_on "SCIM client"
end
expect(page).to have_current_path(new_admin_scim_client_path)
end
end
context "when there are SCIM clients" do
let!(:sso_client) { create(:scim_client) }
it "renders a proper clients table" do
visit admin_scim_clients_path
expect(page).to be_axe_clean.within "#content"
within_test_selector("Admin::ScimClients::TableComponent") do
within(".name") { expect(page).to have_content(sso_client.name) }
within(".authentication_method") { expect(page).to have_content("JWT from identity provider") }
click_on(sso_client.name)
end
expect(page).to have_current_path(edit_admin_scim_client_path(sso_client))
end
end
end
@@ -0,0 +1,166 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
require "spec_helper"
RSpec.describe "Updating a SCIM client", :js, :selenium, driver: :firefox_de do
shared_let(:admin) { create(:admin, preferences: { time_zone: "Etc/UTC" }) }
shared_let(:auth_provider) { create(:oidc_provider) }
let(:sso_scim_client) { create(:scim_client, auth_provider:) }
let(:oauth_client_scim_client) { create(:scim_client, :oauth2_client, auth_provider:) }
let(:token_scim_client) { create(:scim_client, :oauth2_token, auth_provider:) }
let!(:static_tokens) do
application = token_scim_client.oauth_application
[
create(:oauth_access_token, application:, created_at: 2.weeks.ago, expires_in: 2.days.to_i),
create(:oauth_access_token, application:, created_at: 1.week.ago, expires_in: 2.weeks.to_i)
]
end
current_user { admin }
it "can update a SCIM client authenticating through JWT", :aggregate_failures do
visit edit_admin_scim_client_path(sso_scim_client)
expect(page).to be_axe_clean.within("#content")
.skipping("link-in-text-block") # https://community.openproject.org/wp/65252
expect(page.find_field("Authentication method", disabled: :all)).to be_disabled
fill_in "Name", with: ""
fill_in "Subject claim", with: ""
click_on "Save"
expect(page).to have_text("Name can't be blank")
expect(page).to have_text("Subject claim can't be blank")
fill_in "Name", with: "New SSO name"
fill_in "Subject claim", with: "new-claim"
click_on "Save"
expect(page).to have_current_path(admin_scim_clients_path)
within_test_selector("Admin::ScimClients::TableComponent") do
expect(page).to have_text("New SSO name")
end
visit edit_admin_scim_client_path(sso_scim_client)
within(".PageHeader") { click_on "Delete" }
page.within_modal("Delete SCIM client") { click_on "Delete" }
expect(page).to have_current_path(admin_scim_clients_path)
expect(ScimClient.where(id: sso_scim_client.id)).to be_empty
end
it "can update a SCIM client authenticating through client credentials", :aggregate_failures do
visit edit_admin_scim_client_path(oauth_client_scim_client)
expect(page).to be_axe_clean.within("#content")
.skipping("link-in-text-block") # https://community.openproject.org/wp/65252
expect(page.find_field("Authentication method", disabled: :all)).to be_disabled
fill_in "Name", with: ""
click_on "Save"
expect(page).to have_text("Name can't be blank")
fill_in "Name", with: "New client credentials name"
click_on "Save"
expect(page).to have_current_path(admin_scim_clients_path)
within_test_selector("Admin::ScimClients::TableComponent") do
expect(page).to have_text("New client credentials name")
end
visit edit_admin_scim_client_path(oauth_client_scim_client)
expect(page).to have_field("Client ID", with: oauth_client_scim_client.oauth_application.uid)
expect(page).to have_no_field("Client secret")
within(".PageHeader") { click_on "Delete" }
page.within_modal("Delete SCIM client") { click_on "Delete" }
expect(page).to have_current_path(admin_scim_clients_path)
expect(ScimClient.where(id: oauth_client_scim_client.id)).to be_empty
end
it "can update a SCIM client authenticating through a static access token", :aggregate_failures do
visit edit_admin_scim_client_path(token_scim_client)
expect(page).to be_axe_clean.within("#content")
.skipping("link-in-text-block") # https://community.openproject.org/wp/65252
expect(page.find_field("Authentication method", disabled: :all)).to be_disabled
fill_in "Name", with: ""
click_on "Save"
expect(page).to have_text("Name can't be blank")
fill_in "Name", with: "New static token name"
click_on "Save"
expect(page).to have_current_path(admin_scim_clients_path)
within_test_selector("Admin::ScimClients::TableComponent") do
expect(page).to have_text("New static token name")
end
visit edit_admin_scim_client_path(token_scim_client)
within_test_selector("Admin::ScimClients::TokenTableComponent") do
expect(page).to have_css(".created_at").twice
expect(page).to have_css(".expires_at").twice
expect(page).to have_text("Expired on").once
expect(page).to have_no_text("Revoked on")
expect(page).to have_test_selector("op-scim-clients--revoke-token-button").once
page.find_test_selector("op-scim-clients--revoke-token-button").click
end
page.within_modal("Revoke static token") { click_on "Revoke" }
within_test_selector("Admin::ScimClients::TokenTableComponent") do
expect(page).to have_text("Revoked on").once
expect(page).to have_no_test_selector("op-scim-clients--revoke-token-button")
end
page.find_test_selector("op-scim-clients--add-token-button").click
within_modal("Token created") do
expect(page).to have_field("Token", with: Doorkeeper::AccessToken.last.token)
click_on("Close")
end
within_test_selector("Admin::ScimClients::TokenTableComponent") do
expect(page).to have_css(".created_at").exactly(3).times
expect(page).to have_css(".expires_at").exactly(3).times
expect(page).to have_test_selector("op-scim-clients--revoke-token-button").once
end
within(".PageHeader") { click_on "Delete" }
page.within_modal("Delete SCIM client") { click_on "Delete" }
expect(page).to have_current_path(admin_scim_clients_path)
expect(ScimClient.where(id: token_scim_client.id)).to be_empty
end
end
@@ -1,82 +0,0 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
require "spec_helper"
RSpec.describe ScimClients::FormModel do
describe ".from_client" do
subject { described_class.from_client(client) }
let(:client) do
create(
:scim_client, service_account: create(:service_account, authentication_provider: auth_provider, external_id: "abc-def"),
authentication_method: :sso,
auth_provider:
)
end
let(:auth_provider) { create(:oidc_provider) }
it "builds a proper FormModel", :aggregate_failures do
expect(subject.name).to eq(client.name)
expect(subject.auth_provider_id).to eq(auth_provider.id)
expect(subject.authentication_method).to eq("sso")
expect(subject.jwt_sub).to eq("abc-def")
end
context "when the auth provider link is missing" do
let(:client) do
create :scim_client, service_account: create(:service_account),
authentication_method: :sso,
auth_provider:
end
it "fills the auth_provider_id correctly" do
expect(subject.auth_provider_id).to eq(auth_provider.id)
end
it "leaves the jwt_sub blank" do
expect(subject.jwt_sub).to be_nil
end
end
end
describe ".from_params" do
subject { described_class.from_params(params) }
let(:params) { { name: "The Client", auth_provider_id: 42, authentication_method: :banana, jwt_sub: "a-sub" } }
it "builds a proper FormModel", :aggregate_failures do
expect(subject.name).to eq("The Client")
expect(subject.auth_provider_id).to eq(42)
expect(subject.authentication_method).to eq("banana")
expect(subject.jwt_sub).to eq("a-sub")
end
end
end
@@ -0,0 +1,68 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
require "spec_helper"
RSpec.describe ScimClients::GenerateStaticTokenService do
subject(:service_result) { described_class.new(scim_client).call }
let(:scim_client) { create(:scim_client, :oauth2_token) }
it "returns a valid token", :aggregate_failures, :freeze_time do
expect(service_result).to be_success
expect(service_result.result.expires_at).to eq(1.year.from_now)
expect(service_result.result.includes_scope?("scim_v2")).to be_truthy # rubocop:disable RSpec/PredicateMatcher
end
it "generates a token" do
expect { subject }.to change(Doorkeeper::AccessToken, :count).by(1)
end
context "when the SCIM client is authenticating through client credentials" do
let(:scim_client) { create(:scim_client, :oauth2_client) }
it { is_expected.to be_failure }
it "does not generate a token" do
expect { subject }.not_to change(Doorkeeper::AccessToken, :count)
end
end
context "when the SCIM client is authenticating through IDP tokens" do
let(:scim_client) { create(:scim_client) }
it { is_expected.to be_failure }
it "does not generate a token" do
expect { subject }.not_to change(Doorkeeper::AccessToken, :count)
end
end
end
@@ -0,0 +1,79 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
require "spec_helper"
RSpec.describe ScimClients::RevokeStaticTokenService do
subject(:service_result) { described_class.new(scim_client).call(token) }
let(:scim_client) { create(:scim_client, :oauth2_token) }
let(:token) { scim_client.oauth_application.access_tokens.create!(expires_in: 60) }
it "revokes the token effective immediately", :aggregate_failures, :freeze_time do
subject
expect(token.reload).to be_revoked
expect(token.revoked_at).to be_within(0.1).of(Time.zone.now)
end
it { is_expected.to be_success }
context "when the token is already revoked", :freeze_time do
let(:token) { scim_client.oauth_application.access_tokens.create!(expires_in: 60, revoked_at: 1.minute.ago) }
it { is_expected.to be_success }
it "doesn't set a new revoked_at" do
subject
expect(token.reload.revoked_at).to be_within(0.1).of(1.minute.ago)
end
end
context "when the token belongs to a different SCIM client" do
let(:token) { create(:scim_client, :oauth2_token).oauth_application.access_tokens.create!(expires_in: 60) }
it { is_expected.to be_failure }
it "does not revoke the token" do
subject
expect(token.reload).not_to be_revoked
end
end
context "when the token belongs to no SCIM client at all" do
let(:token) { create(:oauth_application).access_tokens.create!(expires_in: 60) }
it { is_expected.to be_failure }
it "does not revoke the token" do
subject
expect(token.reload).not_to be_revoked
end
end
end
@@ -48,6 +48,10 @@ module TestSelectorFinders
def have_test_selector(value, **)
have_selector(test_selector(value), **)
end
def have_no_test_selector(value, **)
have_no_selector(test_selector(value), **)
end
end
RSpec.configure do |config|