Skip to content
Draft
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
22 changes: 19 additions & 3 deletions app/controllers/oidc/pending_trusted_publishers_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,22 @@ def index
end

def new
pending_trusted_publisher = current_user.oidc_pending_trusted_publishers.new(trusted_publisher: OIDC::TrustedPublisher::GitHubAction.new)
selected_trusted_publisher_type = nil
if params[:trusted_publisher_type].present?
selected_trusted_publisher_type = OIDC::TrustedPublisher.all.find { |type| type.url_identifier == params[:trusted_publisher_type] }
end

pending_trusted_publisher = current_user.oidc_pending_trusted_publishers.new
pending_trusted_publisher.trusted_publisher = if selected_trusted_publisher_type
selected_trusted_publisher_type.new
else
OIDC::TrustedPublisher::GitHubAction.new
end

render OIDC::PendingTrustedPublishers::NewView.new(
pending_trusted_publisher:
pending_trusted_publisher: pending_trusted_publisher,
trusted_publisher_types: OIDC::TrustedPublisher.all,
selected_trusted_publisher_type: selected_trusted_publisher_type
)
end

Expand All @@ -30,8 +43,11 @@ def create
redirect_to profile_oidc_pending_trusted_publishers_path, flash: { notice: t(".success") }
else
flash.now[:error] = trusted_publisher.errors.full_messages.to_sentence
selected_trusted_publisher_type = OIDC::TrustedPublisher.all.find { |type| type.polymorphic_name == create_params[:trusted_publisher_type] }
render OIDC::PendingTrustedPublishers::NewView.new(
pending_trusted_publisher: trusted_publisher
pending_trusted_publisher: trusted_publisher,
trusted_publisher_types: OIDC::TrustedPublisher.all,
selected_trusted_publisher_type: selected_trusted_publisher_type
), status: :unprocessable_content
end
end
Expand Down
60 changes: 33 additions & 27 deletions app/controllers/oidc/rubygem_trusted_publishers_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,46 @@ def index
end

def new
selected_trusted_publisher_type = nil
if params[:trusted_publisher_type].present?
selected_trusted_publisher_type = OIDC::TrustedPublisher.all.find { |type| type.url_identifier == params[:trusted_publisher_type] }
end

rubygem_trusted_publisher_instance = @rubygem.oidc_rubygem_trusted_publishers.new

rubygem_trusted_publisher_instance.trusted_publisher = if selected_trusted_publisher_type
selected_trusted_publisher_type.new
else
OIDC::TrustedPublisher::GitHubAction.new
end

render OIDC::RubygemTrustedPublishers::NewView.new(
rubygem_trusted_publisher: @rubygem.oidc_rubygem_trusted_publishers.new(trusted_publisher: gh_actions_trusted_publisher)
rubygem_trusted_publisher: rubygem_trusted_publisher_instance,
trusted_publisher_types: OIDC::TrustedPublisher.all,
selected_trusted_publisher_type: selected_trusted_publisher_type
)
end

def create
trusted_publisher = authorize @rubygem.oidc_rubygem_trusted_publishers.new(create_params)
permitted_params = create_params
trusted_publisher_type = @trusted_publisher_type
specific_trusted_publisher_params = permitted_params[:trusted_publisher_attributes] || {}

specific_trusted_publisher = trusted_publisher_type.build_trusted_publisher(specific_trusted_publisher_params)

rubygem_trusted_publisher = @rubygem.oidc_rubygem_trusted_publishers.new(
trusted_publisher: specific_trusted_publisher
)
trusted_publisher = authorize rubygem_trusted_publisher
if trusted_publisher.save
redirect_to rubygem_trusted_publishers_path(@rubygem.slug), flash: { notice: t(".success") }
redirect_to rubygem_trusted_publishers_path(@rubygem.slug),
flash: { notice: t(".success") }
else
flash.now[:error] = trusted_publisher.errors.full_messages.to_sentence
render OIDC::RubygemTrustedPublishers::NewView.new(
rubygem_trusted_publisher: trusted_publisher
rubygem_trusted_publisher: trusted_publisher,
trusted_publisher_types: OIDC::TrustedPublisher.all,
selected_trusted_publisher_type: trusted_publisher_type
), status: :unprocessable_content
end
end
Expand All @@ -42,10 +69,8 @@ def destroy

def create_params
params.expect(
create_params_key => [
:trusted_publisher_type,
trusted_publisher_attributes: @trusted_publisher_type.permitted_attributes
]
create_params_key => [:trusted_publisher_type,
trusted_publisher_attributes: @trusted_publisher_type.permitted_attributes]
)
end

Expand All @@ -59,23 +84,4 @@ def find_rubygem
def find_rubygem_trusted_publisher
@rubygem_trusted_publisher = authorize @rubygem.oidc_rubygem_trusted_publishers.find(params[:id])
end

def gh_actions_trusted_publisher
github_params = helpers.github_params(@rubygem)

publisher = OIDC::TrustedPublisher::GitHubAction.new
if github_params
publisher.repository_owner = github_params[:user]
publisher.repository_name = github_params[:repo]
publisher.workflow_filename = workflow_filename(publisher.repository)
end
publisher
end

def workflow_filename(repo)
paths = Octokit.contents(repo, path: ".github/workflows").lazy.select { it.type == "file" }.map(&:name).grep(/\.ya?ml\z/)
paths.max_by { |path| [path.include?("release"), path.include?("push")].map! { (it && 1) || 0 } }
rescue Octokit::NotFound, Octokit::InvalidRepository
nil
end
end
2 changes: 2 additions & 0 deletions app/helpers/rubygems_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ def link_to_pusher(api_key_owner)
case api_key_owner
when OIDC::TrustedPublisher::GitHubAction
image_tag "github_icon.png", width: 48, height: 48, theme: :light, alt: "GitHub", title: api_key_owner.name
when OIDC::TrustedPublisher::GitLab
image_tag "gitlab_icon.png", width: 48, height: 48, theme: :light, alt: "GitLab", title: api_key_owner.name
else
raise ArgumentError, "unknown api_key_owner type #{api_key_owner.class}"
end
Expand Down
8 changes: 8 additions & 0 deletions app/javascript/controllers/form_submit_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// app/javascript/controllers/form_submit_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
submitForm() {
this.element.requestSubmit();
}
}
12 changes: 12 additions & 0 deletions app/models/oidc/provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,25 @@ class OIDC::Provider < ApplicationRecord
has_many :audits, as: :auditable, dependent: :nullify

GITHUB_ACTIONS_ISSUER = "https://token.actions.githubusercontent.com".freeze
# GITLAB_ISSUER = "https://gitlab.com".freeze
GITLAB_ISSUER = "https://gdk.test:3000".freeze

def self.github_actions
find_by(issuer: GITHUB_ACTIONS_ISSUER)
end

def self.gitlab
find_by(issuer: GITLAB_ISSUER)
end

def github_actions?
issuer == GITHUB_ACTIONS_ISSUER
end

def gitlab?
issuer == GITLAB_ISSUER
end

class Configuration < ::OpenIDConnect::Discovery::Provider::Config::Response
attr_optional required_attributes.delete(:authorization_endpoint)

Expand All @@ -43,6 +53,8 @@ def trusted_publisher_class
case issuer
when GITHUB_ACTIONS_ISSUER
OIDC::TrustedPublisher::GitHubAction
when GITLAB_ISSUER
OIDC::TrustedPublisher::GitLab
end
end

Expand Down
2 changes: 1 addition & 1 deletion app/models/oidc/trusted_publisher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ def self.table_name_prefix
end

def self.all
[GitHubAction]
[GitHubAction, GitLab]
end
end
4 changes: 4 additions & 0 deletions app/models/oidc/trusted_publisher/github_action.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ def self.build_trusted_publisher(params)

def self.publisher_name = "GitHub Actions"

def self.url_identifier = "github_actions"

def self.form_component = OIDC::TrustedPublisher::GitHubAction::FormComponent

def payload
{
name:,
Expand Down
121 changes: 121 additions & 0 deletions app/models/oidc/trusted_publisher/gitlab.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
class OIDC::TrustedPublisher::GitLab < ApplicationRecord
has_many :rubygem_trusted_publishers, class_name: "OIDC::RubygemTrustedPublisher", as: :trusted_publisher, dependent: :destroy,
inverse_of: :trusted_publisher
has_many :pending_trusted_publishers, class_name: "OIDC::PendingTrustedPublisher", as: :trusted_publisher, dependent: :destroy,
inverse_of: :trusted_publisher
has_many :rubygems, through: :rubygem_trusted_publishers
has_many :api_keys, dependent: :destroy, inverse_of: :owner, as: :owner

validates :project_path, presence: true, length: { maximum: Gemcutter::MAX_FIELD_LENGTH }
validates :environment, :ci_config_ref_uri, :ref_path, length: { maximum: Gemcutter::MAX_FIELD_LENGTH }, allow_blank: true
validate :unique_publisher
validate :ci_config_ref_uri_format

def self.for_claims(claims)
required = {
project_path: claims.fetch(:project_path)
}
base = where(required)

if (env = claims[:environment])
base.where(environment: env).or(base.where(environment: nil)).order(environment: :asc)
else
base.where(environment: nil)
end.first!
end

def self.permitted_attributes
%i[project_path ref_path environment ci_config_ref_uri]
end

def self.build_trusted_publisher(params)
mapped_params = {
project_path: params[:project_path],
ci_config_ref_uri: params[:ci_config_ref_uri],
environment: params[:environment],
ref_path: params[:ref_path]
}
mapped_params[:environment] = nil if mapped_params[:environment].blank?
find_or_initialize_by(mapped_params)
end

def self.publisher_name = "GitLab"

def self.url_identifier = "gitlab"

def self.form_component = OIDC::TrustedPublisher::GitLab::FormComponent

def payload
{
name:,
project_path:,
ref_path:,
environment:,
ci_config_ref_uri:
}
end

delegate :as_json, to: :payload

def to_access_policy(_jwt)
conditions = [
OIDC::AccessPolicy::Statement::Condition.new(
operator: "string_equals",
claim: "project_path",
value: project_path
),
OIDC::AccessPolicy::Statement::Condition.new(
operator: "string_equals",
claim: "aud",
# value: Gemcutter::HOST
value: "http://host.docker.internal:3000"
)
]
if environment.present?
conditions << OIDC::AccessPolicy::Statement::Condition.new(
operator: "string_equals",
claim: "environment",
value: environment
)
end

OIDC::AccessPolicy.new(
statements: [
OIDC::AccessPolicy::Statement.new(
effect: "allow",
principal: OIDC::AccessPolicy::Statement::Principal.new(
oidc: OIDC::Provider::GITLAB_ISSUER
),
conditions: conditions
)
]
)
end

def name
name = "#{self.class.publisher_name} #{project_path} @ #{ci_config_ref_uri}"
name << " (#{environment})" if environment?
name
end

def owns_gem?(rubygem) = rubygem_trusted_publishers.exists?(rubygem: rubygem)

private

def unique_publisher
return unless self.class.exists?(
project_path: project_path,
ci_config_ref_uri: ci_config_ref_uri,
environment: environment
)

errors.add(:base, "publisher already exists")
end

def ci_config_ref_uri_format
return if ci_config_ref_uri.blank?

errors.add(:ci_config_ref_uri, "must end with .yml or .yaml") unless /\.ya?ml\z/.match?(ci_config_ref_uri)
errors.add(:ci_config_ref_uri, "must be a filename only, without directories") if ci_config_ref_uri.include?("/")
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# frozen_string_literal: true

class OIDC::TrustedPublisher::GitLab::FormComponent < ApplicationComponent
prop :gitlab_form, reader: :public

def view_template
gitlab_form.fields_for :trusted_publisher do |trusted_publisher_form|
field trusted_publisher_form, :text_field, :project_path, autocomplete: :off
field trusted_publisher_form, :text_field, :ref_path, autocomplete: :off
field trusted_publisher_form, :text_field, :environment, autocomplete: :off, optional: true
field trusted_publisher_form, :text_field, :ci_config_ref_uri, autocomplete: :off, optional: true
end
end

private

def field(form, type, name, optional: false, **)
form.label name, class: "form__label" do
plain form.object.class.human_attribute_name(name)

span(class: "t-text--s") { " (#{t('form.optional')})" } if optional
end
form.send(type, name, class: class_names("form__input", "tw-border tw-border-red-500" => form.object.errors.include?(name)), **)
p(class: "form__field__instructions") { t("oidc.trusted_publisher.gitlab.#{name}_help_html") }
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
class OIDC::TrustedPublisher::GitLab::TableComponent < ApplicationComponent
prop :gitlab, reader: :public

def view_template
dl(class: "tw-flex tw-flex-col sm:tw-grid sm:tw-grid-cols-2 tw-items-baseline tw-gap-4 full-width overflow-wrap") do
dt(class: "description__heading ") { "GitLab Project Path" }
dd { code { gitlab.project_path } }

dt(class: "description__heading ") { "GitLab Ref Path" }
dd { code { gitlab.ref_path } }

if gitlab.environment.present?
dt(class: "description__heading ") { "GitLab Environment" }
dd { code { gitlab.environment } }
end

if gitlab.ci_config_ref_uri.present?
dt(class: "description__heading ") { "GitLab CI Config Ref URI" }
dd { code { gitlab.ci_config_ref_uri } }
end
end
end
end
Loading