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

Mark gem as archived #4960

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
2 changes: 2 additions & 0 deletions app/avo/resources/rubygem_resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ class IndexedFilter < ScopeBooleanFilter; end
field :link_verifications, as: :has_many
field :oidc_rubygem_trusted_publishers, as: :has_many

field :archived, as: :boolean

field :events, as: :has_many
field :audits, as: :has_many
end
Expand Down
19 changes: 19 additions & 0 deletions app/controllers/api/v1/archive_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
class Api::V1::ArchiveController < Api::BaseController
before_action :authenticate_with_api_key
before_action :verify_with_otp
before_action :find_rubygem

def create
authorize @rubygem, :archive?
@rubygem.archive!(@api_key.user)

render plain: response_with_mfa_warning("#{@rubygem.name} was succesfully archived.")
end

def destroy
authorize @rubygem, :unarchive?
@rubygem.unarchive!

render plain: response_with_mfa_warning("#{@rubygem.name} was succesfully unarchived.")
end
end
31 changes: 31 additions & 0 deletions app/controllers/archive_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
class ArchiveController < ApplicationController
include SessionVerifiable

verify_session_before
before_action :find_rubygem
before_action :verify_mfa_requirement

def show
end

def create
authorize @rubygem, :archive?
@rubygem.archive!(current_user)

redirect_to rubygem_path(@rubygem), notice: t(".success")
end

def destroy
authorize @rubygem, :unarchive?
@rubygem.unarchive!

redirect_to rubygem_path(@rubygem), notice: t(".success")
end

private

def verify_mfa_requirement
return if @rubygem.mfa_requirement_satisfied_for?(current_user)
index_with_error t("owners.mfa_required"), :forbidden

Check warning on line 29 in app/controllers/archive_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/archive_controller.rb#L29

Added line #L29 was not covered by tests
end
end
3 changes: 3 additions & 0 deletions app/controllers/rubygems_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ def index
def show
@versions = @rubygem.public_versions.limit(5)
@adoption = @rubygem.ownership_call

flash[:notice] = t(".archived_notice") if @rubygem.archived?

if @versions.to_a.any?
render "show"
else
Expand Down
8 changes: 8 additions & 0 deletions app/helpers/rubygems_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@
link_to t("rubygems.aside.links.trusted_publishers"), rubygem_trusted_publishers_path(rubygem.slug), class: "gem__link t-list__item"
end

def rubygems_archive_link(rubygem)
link_to t("rubges.aside.links.archive"), rubygems_archive_path(rubygem.slug), class: "gem__link t-list__item", method: "post"

Check warning on line 101 in app/helpers/rubygems_helper.rb

View check run for this annotation

Codecov / codecov/patch

app/helpers/rubygems_helper.rb#L101

Added line #L101 was not covered by tests
end

def rubygems_unarchive_link(rubygem)
link_to t("rubges.aside.links.unarchive"), rubygems_archive_path(rubygem.slug), class: "gem__link t-list__item", method: "delete"

Check warning on line 105 in app/helpers/rubygems_helper.rb

View check run for this annotation

Codecov / codecov/patch

app/helpers/rubygems_helper.rb#L105

Added line #L105 was not covered by tests
end

def oidc_api_key_role_links(rubygem)
roles = current_user.oidc_api_key_roles.for_rubygem(rubygem)

Expand Down
5 changes: 3 additions & 2 deletions app/models/api_key.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
class ApiKey < ApplicationRecord
class ScopeError < RuntimeError; end

API_SCOPES = %i[show_dashboard index_rubygems push_rubygem yank_rubygem add_owner remove_owner access_webhooks
API_SCOPES = %i[show_dashboard index_rubygems push_rubygem yank_rubygem archive_rubygem unarchive_rubygem add_owner remove_owner access_webhooks
configure_trusted_publishers].freeze
APPLICABLE_GEM_API_SCOPES = %i[push_rubygem yank_rubygem add_owner remove_owner configure_trusted_publishers].freeze
APPLICABLE_GEM_API_SCOPES = %i[push_rubygem yank_rubygem archive_rubygem unarchive_rubygem add_owner remove_owner
configure_trusted_publishers].freeze
EXCLUSIVE_SCOPES = %i[show_dashboard].freeze

self.ignored_columns += API_SCOPES
Expand Down
8 changes: 7 additions & 1 deletion app/models/pusher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def initialize(api_key, body, request: nil)

def process
trace("gemcutter.pusher.process", tags: { "gemcutter.api_key.owner" => owner.to_gid }) do
pull_spec && find && authorize && verify_gem_scope && verify_mfa_requirement && validate && save
pull_spec && find && authorize && verify_gem_scope && verify_not_archived && verify_mfa_requirement && validate && save
end
end

Expand All @@ -32,6 +32,12 @@ def verify_gem_scope
notify("This API key cannot perform the specified action on this gem.", 403)
end

def verify_not_archived
return true unless rubygem.archived?

notify("This gem has been archived, and is in a read-only state.", 401)
end

def verify_mfa_requirement
(!api_key.user? || owner.mfa_enabled?) || !(version_mfa_required? || rubygem.metadata_mfa_required?) ||
notify("Rubygem requires owners to enable MFA. You must enable MFA before pushing new version.", 403)
Expand Down
18 changes: 18 additions & 0 deletions app/models/rubygem.rb
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ def create_gem_download
joins(:gem_download).order("MAX(gem_downloads.count) DESC").news(days)
}

scope :unmaintained, -> { where(unmaintained: true) }

def self.letterize(letter)
/\A[A-Za-z]\z/.match?(letter) ? letter.upcase : "A"
end
Expand Down Expand Up @@ -372,6 +374,22 @@ def linkable_verification_uri
URI.join("https://rubygems.org/gems/", name)
end

def archive!(actor)
update!(
archived: true,
archived_at: Time.current,
archived_by: actor.id
)
end

def unarchive!
update!(
archived: false,
archived_at: nil,
archived_by: nil
)
end

private

# a gem namespace is not protected if it is
Expand Down
14 changes: 14 additions & 0 deletions app/policies/api/rubygem_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,18 @@ def configure_trusted_publishers?
api_key_scope?(:configure_trusted_publishers, rubygem) &&
user_authorized?(rubygem, :configure_trusted_publishers?)
end

def archive?
user_api_key? &&
mfa_requirement_satisfied?(rubygem) &&
api_key_scope?(:archive_rubygem, rubygem) &&
user_authorized?(rubygem, :archive?)
end

def unarchive?
user_api_key? &&
mfa_requirement_satisfied?(rubygem) &&
api_key_scope?(:unarchive_rubygem, rubygem) &&
user_authorized?(rubygem, :unarchive?)
end
end
8 changes: 8 additions & 0 deletions app/policies/rubygem_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,12 @@ def add_owner?
def remove_owner?
rubygem_owned_by?(user)
end

def archive?
rubygem_owned_by?(user)
end

def unarchive?
rubygem_owned_by?(user)
end
end
6 changes: 6 additions & 0 deletions config/locales/de.yml
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,7 @@ de:
show_all_versions: Zeige alle Versionen (%{count} total)
versions_header: Versionen
yanked_notice:
archived_notice:
show_yanked:
not_hosted_notice: Dieses Gem wird aktuell nicht auf RubyGems.org gehostet.
reserved_namespace_html:
Expand Down Expand Up @@ -1055,3 +1056,8 @@ de:
api_key_gem_html:
api_key_mfa:
not_required:
archive:
create:
success:
destroy:
success:
6 changes: 6 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,7 @@ en:
show_all_versions: Show all versions (%{count} total)
versions_header: Versions
yanked_notice: This version has been yanked, and it is not available for download directly or for other gems that may have depended on it.
archived_notice: This gem has been archived. It is now read-only and will not accept new versions.
show_yanked:
not_hosted_notice: This gem is not currently hosted on RubyGems.org. Yanked versions of this gem may already exist.
reserved_namespace_html:
Expand Down Expand Up @@ -976,3 +977,8 @@ en:
api_key_gem_html: "Gem: %{gem}"
api_key_mfa: "MFA: %{mfa}"
not_required: "Not required"
archive:
create:
success: "%{gem} was successfully archvied"
destroy:
success: "%{gem} was Successfully unarchived"
6 changes: 6 additions & 0 deletions config/locales/es.yml
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,7 @@ es:
versions_header: Versiones
yanked_notice: Esta versión fue borrada, y no está disponible para su descarga
directa ni por otras gemas que puedan haber dependido de la misma.
archived_notice:
show_yanked:
not_hosted_notice: Esta gema no está alojada actualmente en RubyGems.org. Es
posible que ya exista alguna versión borrada de esta gema.
Expand Down Expand Up @@ -1110,3 +1111,8 @@ es:
api_key_gem_html:
api_key_mfa:
not_required:
archive:
create:
success:
destroy:
success:
6 changes: 6 additions & 0 deletions config/locales/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,7 @@ fr:
versions_header: Versions
yanked_notice: 'Retrait de Gem : disponible ni directement, ni pour les gems
qui en dépendraient.'
archived_notice:
show_yanked:
not_hosted_notice: Gem non hébergé sur Rubygems pour le moment.
reserved_namespace_html:
Expand Down Expand Up @@ -1005,3 +1006,8 @@ fr:
api_key_gem_html:
api_key_mfa:
not_required:
archive:
create:
success:
destroy:
success:
6 changes: 6 additions & 0 deletions config/locales/ja.yml
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,7 @@ ja:
show_all_versions: 全てのバージョンを表示(全%{count}件)
versions_header: バージョン履歴
yanked_notice: このバージョンはヤンクされ、直接のダウンロードや依存関係になっている可能性がある他のgemは利用できません。
archived_notice:
show_yanked:
not_hosted_notice: このgemは現在RubyGems.org上ではホストされていません。このgemのヤンクされたバージョンはまだ存在する可能性があります。
reserved_namespace_html:
Expand Down Expand Up @@ -981,3 +982,8 @@ ja:
api_key_gem_html: 'gem: %{gem}'
api_key_mfa: 'MFA: %{mfa}'
not_required: 必要ではありません
archive:
create:
success:
destroy:
success:
6 changes: 6 additions & 0 deletions config/locales/nl.yml
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,7 @@ nl:
show_all_versions: Toon alle versies (%{count} totaal)
versions_header: Versies
yanked_notice:
archived_notice:
show_yanked:
not_hosted_notice: Deze gem wordt momenteel niet gehost op rubygems.org.
reserved_namespace_html:
Expand Down Expand Up @@ -960,3 +961,8 @@ nl:
api_key_gem_html:
api_key_mfa:
not_required:
archive:
create:
success:
destroy:
success:
6 changes: 6 additions & 0 deletions config/locales/pt-BR.yml
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,7 @@ pt-BR:
show_all_versions: Mostrar todas as versões (%{count})
versions_header: Versões
yanked_notice: Esta gem foi removida, e não está mais disponível para download.
archived_notice:
show_yanked:
not_hosted_notice: Esta gem não está hospedada no Gemcutter.
reserved_namespace_html:
Expand Down Expand Up @@ -983,3 +984,8 @@ pt-BR:
api_key_gem_html:
api_key_mfa:
not_required:
archive:
create:
success:
destroy:
success:
6 changes: 6 additions & 0 deletions config/locales/zh-CN.yml
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,7 @@ zh-CN:
show_all_versions: 显示所有版本 (共 %{count} 个)
versions_header: 版本列表
yanked_notice: 这个 Gem 版本已经撤回了,无法直接下载,也无法被其他 Gem 依赖。
archived_notice:
show_yanked:
not_hosted_notice: 这个 Gem 目前没有被托管在 RubyGems.org 中。这个 Gem 撤回的版本可能已经存在了。
reserved_namespace_html:
Expand Down Expand Up @@ -974,3 +975,8 @@ zh-CN:
api_key_gem_html:
api_key_mfa:
not_required:
archive:
create:
success:
destroy:
success:
6 changes: 6 additions & 0 deletions config/locales/zh-TW.yml
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,7 @@ zh-TW:
show_all_versions: 顯示所有版本(共 %{count})
versions_header: 版本列表
yanked_notice: 這個 Gem 版本已被移除,因此無法提供下載,也無法被其他的 Gem 相依。
archived_notice:
show_yanked:
not_hosted_notice: 這個 Gem 目前沒有在 RubyGems.org 上
reserved_namespace_html:
Expand Down Expand Up @@ -965,3 +966,8 @@ zh-TW:
api_key_gem_html:
api_key_mfa:
not_required:
archive:
create:
success:
destroy:
success:
7 changes: 7 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@
constraints rubygem_id: Patterns::ROUTE_PATTERN do
resource :owners, only: %i[show create destroy]
resources :trusted_publishers, controller: 'oidc/rubygem_trusted_publishers', only: %i[index create destroy show]

post :archive, to: "archive#create"
delete :archive, to: "archive#destroy"
end
end

Expand Down Expand Up @@ -223,6 +226,10 @@
end
resources :adoptions, only: %i[index]
resources :trusted_publishers, controller: 'oidc/rubygem_trusted_publishers', only: %i[index create destroy new]

get 'archive', to: 'archive#show'
post 'archive', to: 'archive#create'
delete 'archive', to: 'archive#destroy'
end

resources :ownership_calls, only: :index
Expand Down
10 changes: 10 additions & 0 deletions db/migrate/20240815014607_add_archived_to_rubygem.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class AddArchivedToRubygem < ActiveRecord::Migration[7.1]
disable_ddl_transaction!

def change
add_column :rubygems, :archived, :boolean, default: false, null: false
add_column :rubygems, :archived_at, :datetime
add_column :rubygems, :archived_by, :integer
add_index :rubygems, :archived, algorithm: :concurrently
end
end
6 changes: 5 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[7.1].define(version: 2024_07_22_182907) do
ActiveRecord::Schema[7.1].define(version: 2024_08_15_014607) do
# These are extensions that must be enabled in order to support this database
enable_extension "hstore"
enable_extension "pgcrypto"
Expand Down Expand Up @@ -464,8 +464,12 @@
t.datetime "created_at", precision: nil
t.datetime "updated_at", precision: nil
t.boolean "indexed", default: false, null: false
t.boolean "archived", default: false, null: false
t.datetime "archived_at"
t.integer "archived_by"
t.index "regexp_replace(upper((name)::text), '[_-]'::text, ''::text, 'g'::text)", name: "dashunderscore_typos_idx"
t.index "upper((name)::text) varchar_pattern_ops", name: "index_rubygems_upcase"
t.index ["archived"], name: "index_rubygems_on_archived"
t.index ["indexed"], name: "index_rubygems_on_indexed"
t.index ["name"], name: "index_rubygems_on_name", unique: true
end
Expand Down
Loading
Loading