Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OAuth application flow for builtin mobile app #15541

Merged
merged 5 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<%=
component_wrapper(data: { 'test-selector': "op-admin-oauth--application" }) do
flex_layout(align_items: :center, justify_content: :space_between) do |oauth_application_container|
oauth_application_container.with_column(flex_layout: true) do |application_information|
application_information.with_column(mr: 2) do
render(Primer::Beta::Link.new(href: oauth_application_path(@application), font_weight: :bold)) do
@application.name
end
end

application_information.with_column(mr: 2) do
render(Primer::Beta::Text.new(font_size: :small, color: :subtle)) do
if @application.builtin?
t("oauth.application.builtin")
else
t(:label_created_by, user: @application.owner)
end
end
end

unless @application.builtin?
application_information.with_column do
render(Primer::Beta::Label.new) do
@application.confidential? ? t("oauth.application.confidential") : t("oauth.application.non_confidential")
end
end
end
end

# Actions
oauth_application_container.with_column do
render(Primer::Alpha::ToggleSwitch.new(
src: toggle_oauth_application_path(@application),
csrf_token: form_authenticity_token,
checked: @application.enabled?,
data: {
'test-selector': "op-admin-oauth--application-enabled-toggle-switch"
}
))
end
end
end
%>
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# frozen_string_literal: true

#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
Expand Down Expand Up @@ -30,50 +28,15 @@

module OAuth
module Applications
class RowComponent < ::RowComponent
property :confidential

def application
model
end

def name
link_to application.name, oauth_application_path(application)
end

def owner
link_to application.owner.name, user_path(application.owner)
end
class ApplicationRowComponent < ApplicationComponent
include ApplicationHelper
include OpPrimer::ComponentHelpers
include OpTurbo::Streamable

def confidential
if application.confidential?
helpers.op_icon "icon icon-checkmark"
end
end

def redirect_uri
urls = application.redirect_uri.split("\n")
safe_join urls, "<br/>".html_safe
end

def client_credentials
if user_id = application.client_credentials_user_id
helpers.link_to_user User.find(user_id)
else
"-"
end
end

def edit_link
link_to(
I18n.t(:button_edit),
edit_oauth_application_path(application),
class: "oauth-application--edit-link icon icon-edit"
)
end
def initialize(application:)
@application = application

def button_links
[edit_link, helpers.delete_link(oauth_application_path(application))]
super
end
end
end
Expand Down
55 changes: 55 additions & 0 deletions app/components/oauth/applications/index_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<%=
component_wrapper do
flex_layout do |index_container|
if OpenProject::FeatureDecisions.built_in_oauth_applications_active?
index_container.with_row do
render(border_box_container(mb: 4, data: {
'test-selector': "op-admin-oauth--built-in-applications"
})) do |component|
component.with_header(font_weight: :bold) do
render(Primer::Beta::Text.new) do
t("oauth.header.builtin_applications")
end
end

if @built_in_applications.empty?
component.with_row do
render(Primer::Beta::Text.new(data: {
'test-selector': "op-admin-oauth--built-in-applications-placeholder"
})) do
t("oauth.empty_application_lists")
end
end
end

@built_in_applications.each do |application|
component.with_row { render(OAuth::Applications::ApplicationRowComponent.new(application:)) }
end
end
end
end

index_container.with_row do
render(border_box_container(mb: 4)) do |component|
component.with_header(font_weight: :bold) do
render(Primer::Beta::Text.new) do
t("oauth.header.other_applications")
end
end

if @other_applications.empty?
component.with_row do
render(Primer::Beta::Text.new(data: { 'test-selector': "op-admin-oauth--applications-placeholder" })) do
t("oauth.empty_application_lists")
end
end
end

@other_applications.each do |application|
component.with_row { render(OAuth::Applications::ApplicationRowComponent.new(application:)) }
end
end
end
end
end
%>
44 changes: 44 additions & 0 deletions app/components/oauth/applications/index_component.rb
Original file line number Diff line number Diff line change
@@ -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.
#++

module OAuth
module Applications
class IndexComponent < ApplicationComponent
include ApplicationHelper
include OpPrimer::ComponentHelpers
include OpTurbo::Streamable

def initialize(oauth_applications:)
@built_in_applications = oauth_applications.select(&:builtin?)
@other_applications = oauth_applications.reject(&:builtin?)

super
end
end
end
end
50 changes: 26 additions & 24 deletions app/components/oauth/show_page_header_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -32,31 +32,33 @@ See COPYRIGHT and LICENSE files for more details.
header.with_title { h(@application.name) }
header.with_breadcrumbs(breadcrumb_items)

header.with_action_button(tag: :a,
mobile_icon: :pencil,
mobile_label: t(:button_edit),
size: :medium,
href: edit_oauth_application_path(@application),
aria: { label: I18n.t(:button_edit) },
title: I18n.t(:button_edit)) do |button|
button.with_leading_visual_icon(icon: :pencil)
t(:button_edit)
end
unless @application.builtin?
header.with_action_button(tag: :a,
mobile_icon: :pencil,
mobile_label: t(:button_edit),
size: :medium,
href: edit_oauth_application_path(@application),
aria: { label: I18n.t(:button_edit) },
title: I18n.t(:button_edit)) do |button|
button.with_leading_visual_icon(icon: :pencil)
t(:button_edit)
end

header.with_action_button(tag: :a,
scheme: :danger,
mobile_icon: :trash,
mobile_label: t(:button_delete),
size: :medium,
href: oauth_application_path(@application),
aria: { label: I18n.t(:button_delete) },
data: {
confirm: I18n.t(:text_are_you_sure),
method: :delete
},
title: I18n.t(:button_delete)) do |button|
button.with_leading_visual_icon(icon: :trash)
t(:button_delete)
header.with_action_button(tag: :a,
scheme: :danger,
mobile_icon: :trash,
mobile_label: t(:button_delete),
size: :medium,
href: oauth_application_path(@application),
aria: { label: I18n.t(:button_delete) },
data: {
confirm: I18n.t(:text_are_you_sure),
method: :delete
},
title: I18n.t(:button_delete)) do |button|
button.with_leading_visual_icon(icon: :trash)
t(:button_delete)
end
end
end
%>
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
# frozen_string_literal: true

#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
# 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.
Expand Down Expand Up @@ -30,46 +28,45 @@

module OAuth
module Applications
class TableComponent < ::TableComponent
class << self
def row_class
::OAuth::Applications::RowComponent
end
class BaseContract < ::ModelContract
def self.model
::Doorkeeper::Application
end

def initial_sort
%i[id asc]
end
validate :validate_client_credential_user
validate :validate_integration
validate :validate_admin_only

def sortable?
false
end
attribute :enabled
attribute :name
attribute :redirect_uri
attribute :confidential
attribute :owner_id
attribute :owner_type
attribute :scopes
attribute :client_credentials_user_id
attribute :integration_id
attribute :integration_type

def columns
headers.map(&:first)
end
private

def inline_create_link
link_to new_oauth_application_path,
aria: { label: t("oauth.application.new") },
class: "wp-inline-create--add-link",
title: t("oauth.application.new") do
helpers.op_icon("icon icon-add")
end
def validate_admin_only
errors.add :base, :error_unauthorized unless user.admin?
end

def empty_row_message
I18n.t :no_results_title_text
def validate_integration
both = model.integration_id.present? && model.integration_type.present?
none = model.integration_id.nil? && model.integration_type.nil?

errors.add :integration, :invalid unless both || none
end

def headers
[
["name", { caption: ::Doorkeeper::Application.human_attribute_name(:name) }],
["owner", { caption: ::Doorkeeper::Application.human_attribute_name(:owner) }],
["client_credentials", { caption: I18n.t("oauth.client_credentials") }],
["redirect_uri", { caption: ::Doorkeeper::Application.human_attribute_name(:redirect_uri) }],
["confidential", { caption: ::Doorkeeper::Application.human_attribute_name(:confidential) }]
]
def validate_client_credential_user
return if model.client_credentials_user_id.blank?
Kharonus marked this conversation as resolved.
Show resolved Hide resolved

unless User.exists?(id: model.client_credentials_user_id)
errors.add :client_credentials_user_id, :invalid
end
end
end
end
Kharonus marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
Loading