<%= link_to t('oidc.api_key_roles.index.api_key_roles'), profile_oidc_api_key_roles_path %>
diff --git a/config/brakeman.ignore b/config/brakeman.ignore
index ab7ee81ab1a..a09e512efd8 100644
--- a/config/brakeman.ignore
+++ b/config/brakeman.ignore
@@ -7,7 +7,7 @@
"check_name": "Render",
"message": "Render path contains parameter value",
"file": "app/controllers/oidc/id_tokens_controller.rb",
- "line": 17,
+ "line": 16,
"link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
"code": "render(action => OIDC::IdTokens::IndexView.new(:id_tokens => current_user.oidc_id_tokens.includes(:api_key, :api_key_role, :provider).page(params[:page].to_i).strict_loading), {})",
"render_path": null,
@@ -30,7 +30,7 @@
"check_name": "Render",
"message": "Render path contains parameter value",
"file": "app/views/oidc/api_key_roles/show.html.erb",
- "line": 20,
+ "line": 25,
"link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
"code": "render(action => current_user.oidc_api_key_roles.includes(:provider).find_by!(:token => params.require(:token)).access_policy, {})",
"render_path": [
@@ -57,6 +57,29 @@
],
"note": ""
},
+ {
+ "warning_type": "Dynamic Render Path",
+ "warning_code": 15,
+ "fingerprint": "175ed1fa3ddae92efe03e70a1fea7df153ac9bd53edf617e0c70f420f4ac6781",
+ "check_name": "Render",
+ "message": "Render path contains parameter value",
+ "file": "app/controllers/oidc/rubygem_trusted_publishers_controller.rb",
+ "line": 11,
+ "link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
+ "code": "render(action => OIDC::RubygemTrustedPublishers::IndexView.new(:rubygem => Rubygem.find_by_name((params[:rubygem_id] or params[:id])), :trusted_publishers => Rubygem.find_by_name((params[:rubygem_id] or params[:id])).oidc_rubygem_trusted_publishers.includes(:trusted_publisher).page(params[:page].to_i).strict_loading), {})",
+ "render_path": null,
+ "location": {
+ "type": "method",
+ "class": "OIDC::RubygemTrustedPublishersController",
+ "method": "index"
+ },
+ "user_input": "params[:page]",
+ "confidence": "Weak",
+ "cwe_id": [
+ 22
+ ],
+ "note": ""
+ },
{
"warning_type": "Dynamic Render Path",
"warning_code": 15,
@@ -110,7 +133,7 @@
"check_name": "Render",
"message": "Render path contains parameter value",
"file": "app/controllers/oidc/providers_controller.rb",
- "line": 13,
+ "line": 12,
"link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
"code": "render(action => OIDC::Providers::IndexView.new(:providers => OIDC::Provider.all.strict_loading.page(params[:page].to_i)), {})",
"render_path": null,
@@ -126,6 +149,29 @@
],
"note": ""
},
+ {
+ "warning_type": "Dynamic Render Path",
+ "warning_code": 15,
+ "fingerprint": "c120032e149023b5d6bb95739c27f2d47bfdb64f78b9bccb2e92c7707bd92a78",
+ "check_name": "Render",
+ "message": "Render path contains parameter value",
+ "file": "app/controllers/oidc/pending_trusted_publishers_controller.rb",
+ "line": 11,
+ "link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
+ "code": "render(action => OIDC::PendingTrustedPublishers::IndexView.new(:trusted_publishers => current_user.oidc_pending_trusted_publishers.unexpired.includes(:trusted_publisher).order(:rubygem_name, :created_at).page(params[:page].to_i).strict_loading), {})",
+ "render_path": null,
+ "location": {
+ "type": "method",
+ "class": "OIDC::PendingTrustedPublishersController",
+ "method": "index"
+ },
+ "user_input": "params[:page]",
+ "confidence": "Weak",
+ "cwe_id": [
+ 22
+ ],
+ "note": ""
+ },
{
"warning_type": "Dynamic Render Path",
"warning_code": 15,
@@ -133,7 +179,7 @@
"check_name": "Render",
"message": "Render path contains parameter value",
"file": "app/views/oidc/api_key_roles/show.html.erb",
- "line": 16,
+ "line": 21,
"link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
"code": "render(action => current_user.oidc_api_key_roles.includes(:provider).find_by!(:token => params.require(:token)).api_key_permissions, {})",
"render_path": [
@@ -167,7 +213,7 @@
"check_name": "Render",
"message": "Render path contains parameter value",
"file": "app/controllers/oidc/providers_controller.rb",
- "line": 17,
+ "line": 16,
"link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
"code": "render(action => OIDC::Providers::ShowView.new(:provider => OIDC::Provider.find(params.require(:id))), {})",
"render_path": null,
@@ -184,6 +230,6 @@
"note": ""
}
],
- "updated": "2023-10-17 10:36:55 -0700",
+ "updated": "2023-11-24 13:29:48 -0600",
"brakeman_version": "6.0.1"
}
diff --git a/config/initializers/datadog.rb b/config/initializers/datadog.rb
index 67a755a616a..2e44a0f4b13 100644
--- a/config/initializers/datadog.rb
+++ b/config/initializers/datadog.rb
@@ -9,7 +9,7 @@
# Enabling datadog functionality
- enabled = (Rails.env.production? || Rails.env.staging?) && ENV["DD_AGENT_HOST"].present? && !defined?(Rails::Console)
+ enabled = !(Rails.env.development? || Rails.env.test?) && ENV["DD_AGENT_HOST"].present? && !defined?(Rails::Console)
c.runtime_metrics.enabled = enabled
c.profiling.enabled = enabled
c.tracing.enabled = enabled
diff --git a/config/locales/de.yml b/config/locales/de.yml
index 93455bda012..b2ca1fe7f70 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -57,6 +57,10 @@ de:
api_key_role:
oidc/api_key_role:
api_key_permissions:
+ oidc/trusted_publisher/github_action:
+ repository_owner_id:
+ oidc/pending_trusted_publisher:
+ rubygem_name:
errors:
messages:
unpwn:
@@ -73,6 +77,14 @@ de:
taken:
full_name:
taken:
+ oidc/rubygem_trusted_publisher:
+ attributes:
+ rubygem:
+ taken:
+ oidc/pending_trusted_publisher:
+ attributes:
+ rubygem_name:
+ unavailable:
models:
user:
activemodel:
@@ -317,6 +329,8 @@ de:
global_html:
gem_text:
gem_html:
+ gem_trusted_publisher_added:
+ title:
news:
show:
title:
@@ -580,6 +594,7 @@ de:
api_key_role:
name:
new:
+ trusted_publishers:
reserved:
reserved_namespace:
dependencies:
@@ -783,6 +798,42 @@ de:
title:
show:
title:
+ rubygem_trusted_publishers:
+ index:
+ title:
+ subtitle_owner_html:
+ delete:
+ create:
+ description_html:
+ destroy:
+ success:
+ create:
+ success:
+ new:
+ title:
+ subtitle_owner_html:
+ pending_trusted_publishers:
+ index:
+ title:
+ valid_for_html:
+ delete:
+ create:
+ description_html:
+ destroy:
+ success:
+ create:
+ success:
+ new:
+ title:
+ trusted_publisher:
+ unsupported_type:
+ github_actions:
+ repository_owner_help_html:
+ repository_name_help_html:
+ workflow_filename_help_html:
+ environment_help_html:
+ pending:
+ rubygem_name_help_html:
duration:
minutes:
other:
@@ -796,3 +847,5 @@ de:
seconds:
other:
one:
+ form:
+ optional:
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 72e1c61f935..06802c17602 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -67,6 +67,10 @@ en:
api_key_role: API Key Role
oidc/api_key_role:
api_key_permissions: API Key Permissions
+ oidc/trusted_publisher/github_action:
+ repository_owner_id: GitHub Repository Owner ID
+ oidc/pending_trusted_publisher:
+ rubygem_name: RubyGem name
errors:
messages:
unpwn: has previously appeared in a data breach and should not be used
@@ -83,6 +87,14 @@ en:
taken: "%{value} already exists"
full_name:
taken: "%{value} already exists"
+ oidc/rubygem_trusted_publisher:
+ attributes:
+ rubygem:
+ taken: "has already been configured with this trusted publisher"
+ oidc/pending_trusted_publisher:
+ attributes:
+ rubygem_name:
+ unavailable: "is already in use"
models:
user: User
activemodel:
@@ -331,6 +343,8 @@ en:
global_html: This webhook was previously called when
any gem was pushed.
gem_text: This webhook was previously called when %{gem} was pushed.
gem_html: This webhook was previously called when
%{gem} was pushed.
+ gem_trusted_publisher_added:
+ title: TRUSTED PUBLISHER ADDED
news:
show:
title: New Releases — All Gems
@@ -580,6 +594,7 @@ en:
api_key_role:
name: "OIDC: %{name}"
new: "OIDC: Create"
+ trusted_publishers: Trusted publishers
reserved:
reserved_namespace: This namespace is reserved by rubygems.org.
dependencies:
@@ -784,6 +799,49 @@ en:
title: "OIDC ID Tokens"
show:
title: "OIDC ID Token"
+ rubygem_trusted_publishers:
+ index:
+ title: Trusted Publishers
+ subtitle_owner_html: "Trusted publishers for %{gem_html}"
+ delete: Delete
+ create: Create
+ description_html: |
+ Trusted publishers allow you to push gems from CI without storing any long-lived sensitive credentials.
+ For more information about how to set up trusted publishing, see the
trusted publishing documentation.
+ destroy:
+ success: "Trusted Publisher deleted"
+ create:
+ success: "Trusted Publisher created"
+ new:
+ title: "New Trusted Publisher"
+ subtitle_owner_html: "Add a trusted publisher for %{gem_html}"
+ pending_trusted_publishers:
+ index:
+ title: Pending Trusted Publishers
+ valid_for_html: "Valid for %{time_html}"
+ delete: Delete
+ create: Create
+ description_html: |
+ Pending trusted publishers allow you to configure trusted publishing before you have pushed the first version of a gem.
+ For more information about how to set up trusted publishing, see the
trusted publishing documentation.
+ destroy:
+ success: "Pending Trusted Publisher deleted"
+ create:
+ success: "Pending Trusted Publisher created"
+ new:
+ title: "New Pending Trusted Publisher"
+ trusted_publisher:
+ unsupported_type: "Unsupported trusted publisher type"
+ github_actions:
+ repository_owner_help_html: "The GitHub organization name or GitHub username that owns the repository"
+ repository_name_help_html: "The name of the GitHub repository that contains the publishing workflow"
+ workflow_filename_help_html: "The filename of the publishing workflow.
This file should exist in the
.github/workflows/
directory in the repository configured above."
+ environment_help_html: |
+ The name of the
GitHub Actions environment that the above workflow uses for publishing.
+ This should be configured under the repository's settings.
+ While not required, a dedicated publishing environment is strongly encouraged, especially if your repository has maintainers with commit access who shouldn't have RubyGems.org gem push access.
+ pending:
+ rubygem_name_help_html: "The gem (on RubyGems.org) that will be created when this publisher is used"
duration:
minutes:
other: "%{count} minutes"
@@ -797,3 +855,5 @@ en:
seconds:
other: "%{count} seconds"
one: "1 second"
+ form:
+ optional: optional
diff --git a/config/locales/es.yml b/config/locales/es.yml
index 0946aba85e2..fa0c414f637 100644
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -68,10 +68,16 @@ es:
api_key_role:
oidc/api_key_role:
api_key_permissions:
+ oidc/trusted_publisher/github_action:
+ repository_owner_id:
+ oidc/pending_trusted_publisher:
+ rubygem_name:
errors:
messages:
- unpwn: ha aparecido con anterioridad en una filtración de datos y no se debería usar
- blocked: "El dominio '%{domain}' ha sido bloqueado por spam. Por favor utiliza un email personal válido."
+ unpwn: ha aparecido con anterioridad en una filtración de datos y no se debería
+ usar
+ blocked: El dominio '%{domain}' ha sido bloqueado por spam. Por favor utiliza
+ un email personal válido.
models:
ownership:
attributes:
@@ -84,6 +90,14 @@ es:
taken: "%{value} ya existe"
full_name:
taken: "%{value} ya existe"
+ oidc/rubygem_trusted_publisher:
+ attributes:
+ rubygem:
+ taken:
+ oidc/pending_trusted_publisher:
+ attributes:
+ rubygem_name:
+ unavailable:
models:
user: Usuario
activemodel:
@@ -96,15 +110,16 @@ es:
oidc/api_key_permissions:
attributes:
valid_for:
- inclusion: "%{value} segundos debe estar entre 5 minutos (300 segundos) y 1 día (86.400 segundos)"
+ inclusion: "%{value} segundos debe estar entre 5 minutos (300 segundos)
+ y 1 día (86.400 segundos)"
gems:
- too_long: "como mucho puede incluir una gema"
+ too_long: como mucho puede incluir una gema
api_keys:
create:
- success: "Nueva clave de API creada"
+ success: Nueva clave de API creada
invalid_gem: La gema seleccionada no se puede incluir en esta clave
destroy:
- success: "Clave de API eliminada con éxito: %{name}"
+ success: 'Clave de API eliminada con éxito: %{name}'
index:
api_keys: Claves de API
name: Nombre
@@ -125,28 +140,34 @@ es:
access_webhooks: Acceso a webhooks
show_dashboard: Mostrar dashboard
reset: Restablecer
- save_key: "Ten en cuenta que no se volverá a mostrar la clave de API. Nueva clave de API:"
+ save_key: 'Ten en cuenta que no se volverá a mostrar la clave de API. Nueva
+ clave de API:'
mfa: AMF
new:
new_api_key: Nueva clave de API
multifactor_auth: Autenticación multifactor
enable_mfa: Activar AMF
rubygem_scope: Ámbito de aplicación
- rubygem_scope_info: Este alcance restringe los comandos de añadir/eliminar gemas y añadir/eliminar propietarios a una gema específica.
+ rubygem_scope_info: Este alcance restringe los comandos de añadir/eliminar gemas
+ y añadir/eliminar propietarios a una gema específica.
reset:
- success: "Borradas todas las claves de API"
+ success: Borradas todas las claves de API
update:
- success: "Clave de API actualizada con éxito"
- invalid_gem: La gema seleccionada no se puede incluir en el alcance de esta clave
+ success: Clave de API actualizada con éxito
+ invalid_gem: La gema seleccionada no se puede incluir en el alcance de esta
+ clave
edit:
edit_api_key: Editar clave de API
multifactor_auth: Autenticación de múltiples factores
enable_mfa: Activar AMF
rubygem_scope: Ámbito de aplicación
- rubygem_scope_info: Este alcance restringe los comandos de añadir/eliminar gemas y añadir/eliminar propietarios a una gema específica.
- invalid_key: No se puede editar una clave de API inválida. Por favor bórrala y crea una nueva.
+ rubygem_scope_info: Este alcance restringe los comandos de añadir/eliminar gemas
+ y añadir/eliminar propietarios a una gema específica.
+ invalid_key: No se puede editar una clave de API inválida. Por favor bórrala
+ y crea una nueva.
all_gems: Todas las gemas
- gem_ownership_removed: Se ha eliminado la propiedad de %{rubygem_name} tras haber sido añadida a esta clave.
+ gem_ownership_removed: Se ha eliminado la propiedad de %{rubygem_name} tras haber
+ sido añadida a esta clave.
clearance_mailer:
change_password:
title: CAMBIAR CONTRASEÑA
@@ -212,7 +233,7 @@ es:
uptime: Uptime
verified_by: Verificado por
secured_by: Protegido por
- looking_for_maintainers: "Se buscan mantenedores/as"
+ looking_for_maintainers: Se buscan mantenedores/as
header:
dashboard: Dashboard
settings: Configuración
@@ -225,18 +246,21 @@ es:
mailer:
confirm_your_email: Por favor confirma tu dirección de correo con el enlace enviado.
confirmation_subject: Por favor confirma tu dirección de correo con %{host}
- link_expiration_explanation_html: Por favor ten en cuenta que este enlace es válido solo durante 3 horas. Puedes solicitar un enlace actualizado usando la página de
reenvío del correo de confirmación.
+ link_expiration_explanation_html: Por favor ten en cuenta que este enlace es válido
+ solo durante 3 horas. Puedes solicitar un enlace actualizado usando la página
+ de
reenvío del correo
+ de confirmación.
email_confirmation:
title: CONFIRMACIÓN DE EMAIL
subtitle: "¡Último paso!"
confirmation_link: Confirma la dirección de correo
- welcome_message: Bienvenidos a %{host}! Haz clic en el enlace de abajo
- para verificar tu dirección de correo.
+ welcome_message: Bienvenidos a %{host}! Haz clic en el enlace de abajo para
+ verificar tu dirección de correo.
email_reset:
title: CAMBIO DE EMAIL
subtitle: "¡Hola %{handle}!"
- visit_link_instructions: Cambiaste tu dirección de correo en %{host}. Por
- favor visita el siguiente enlace para reactivar tu cuenta.
+ visit_link_instructions: Cambiaste tu dirección de correo en %{host}. Por favor
+ visita el siguiente enlace para reactivar tu cuenta.
deletion_complete:
title: ELIMINACIÓN COMPLETADA
subtitle: "¡Adiós!"
@@ -247,15 +271,15 @@ es:
title: ELIMINACIÓN FALLIDA
subtitle: "¡Lo sentimos!"
subject: Tu solicitud para eliminar tu cuenta de %{host} ha fallado
- body_html: Has solicitado eliminar tu cuenta de %{host}. Lamentablemente,
- no hemos podido procesar tu pedido. Por favor inténtalo de nuevo más adelante
+ body_html: Has solicitado eliminar tu cuenta de %{host}. Lamentablemente, no
+ hemos podido procesar tu pedido. Por favor inténtalo de nuevo más adelante
o %{contact} si el problema persiste.
notifiers_changed:
subject: Cambiaste la configuración de tus notificaciones por email de %{host}
title: NOTIFICACIONES POR EMAIL
subtitle: "¡Hola %{handle}!"
- "on": "ON"
- off_html:
OFF
+ 'on': 'ON'
+ off_html: "
OFF"
gem_pushed:
subject: Gema %{gem} subida a %{host}
title: GEMA SUBIDA
@@ -263,7 +287,7 @@ es:
subject: Gema %{gem} eliminada de %{host}
title: GEMA ELIMINADA
reset_api_key:
- subject: "Clave de API de %{host} restablecida"
+ subject: Clave de API de %{host} restablecida
title: CLAVE DE API RESTABLECIDA
subtitle: "¡Hola %{handle}!"
webauthn_credential_created:
@@ -289,29 +313,44 @@ es:
subject: Por favor confirma la propiedad de la gema %{gem} en %{host}
title: CONFIMACIÓN DE PROPIEDAD
subtitle: "¡Hola %{handle}!"
- body_text: Has sido añadido/a como propietario/a de la gema %{gem} por %{authorizer}. Por favor visita el enlace siguiente para confirmarlo.
- body_html: Has sido añadido/a como propietario/a de la gema
%{gem} por
%{authorizer}. Por favor visita el enlace siguiente para confirmarlo.
- link_expiration_explanation_html: Ten en cuenta que este enlace es válido solo durante %{expiry_hours}. Puedes reenviar el correo de confirmación desde la página de la gema
%{gem} una vez autenticado.
+ body_text: Has sido añadido/a como propietario/a de la gema %{gem} por %{authorizer}.
+ Por favor visita el enlace siguiente para confirmarlo.
+ body_html: Has sido añadido/a como propietario/a de la gema
%{gem}
+ por
%{authorizer}. Por favor visita el enlace siguiente para
+ confirmarlo.
+ link_expiration_explanation_html: Ten en cuenta que este enlace es válido solo
+ durante %{expiry_hours}. Puedes reenviar el correo de confirmación desde la
+ página de la gema
%{gem}
+ una vez autenticado.
owner_added:
subject_self: Has sido añadido/a como propietario/a de la gema %{gem}
- subject_others: El usuario %{owner_handle} ha sido añadido/a como propietario/a de la gema %{gem}
+ subject_others: El usuario %{owner_handle} ha sido añadido/a como propietario/a
+ de la gema %{gem}
title: PROPIETARIO AÑADIDO
subtitle: "¡Hola %{handle}!"
- body_self_html:
Has sido añadido/a como propietario/a de la gema
%{gem} en %{host}.
- body_others_html: El usuario
%{owner_handle} ha sido añadido/a como propietario/a de la gema
%{gem} por
%{authorizer}. Recibes esta notificación por ser propietario de %{gem}.
+ body_self_html:
Has sido añadido/a como propietario/a de la gema
%{gem} en %{host}.
+ body_others_html: El usuario
%{owner_handle} ha sido añadido/a como propietario/a
+ de la gema
%{gem}
+ por
%{authorizer}. Recibes esta notificación por ser propietario de
+ %{gem}.
owner_removed:
subject: Has sido eliminado como propietario/a de la gema %{gem}
title: PROPIETARIO ELIMINADO
subtitle: "¡Hola %{handle}!"
- body_html: Has sido eliminado como propietario/a de la gema
%{gem} en %{host} por
%{remover}.
+ body_html: Has sido eliminado como propietario/a de la gema
%{gem}
+ en %{host} por
%{remover}.
ownerhip_request_closed:
title: CANDIDATURA A PROPIETARIO
subtitle: "¡Hola %{handle}!"
- body_html: Gracias por proponerte como propietario para
%{gem}. Lamentamos informarte de que el dueño de la gema ha cerrado tu solicitud.
+ body_html: Gracias por proponerte como propietario para
%{gem}.
+ Lamentamos informarte de que el dueño de la gema ha cerrado tu solicitud.
ownerhip_request_approved:
- body_html: ¡Enhorabuena! Tu candidatura a propietario de
%{gem} ha sido aprobada. Se te ha añadido a la lista de propietarios de la gema.
+ body_html: "¡Enhorabuena! Tu candidatura a propietario de
%{gem}
+ ha sido aprobada. Se te ha añadido a la lista de propietarios de la gema."
new_opwnership_requests:
- body_html: Hay
%{count} nuevas candidaturas a propietario para
%{gem}. Por favor haz click en el botón siguiente para ver todas las candidaturas.
+ body_html: Hay
%{count} nuevas candidaturas a propietario para
%{gem}.
+ Por favor haz click en el botón siguiente para ver todas las candidaturas.
button: CANDIDATURAS A PROPIETARIO
disable_notifications: Para dejar de recibir estos mensajes actualiza tus
owners_page: PROPIETARIOS
@@ -319,10 +358,13 @@ es:
title: WEBHOOK ELIMINADO
subject: Se ha borrado tu webhook en %{host}
subtitle: "¡Hola %{handle}!"
- body_text: Tu webhook que enviaba peticiones POST a %{url} ha sido eliminado tras %{failures} fallos.
- body_html: Tu webhook que enviaba peticiones
POST
a
%{url}
ha sido eliminado tras %{failures} fallos
+ body_text: Tu webhook que enviaba peticiones POST a %{url} ha sido eliminado
+ tras %{failures} fallos.
+ body_html: Tu webhook que enviaba peticiones
POST
a
%{url}
+ ha sido eliminado tras %{failures} fallos
global_text: Este webhook se ejecutaba antes cuando se subía cualquier gema.
- global_html: Este webhook se ejecutaba antes cuando se subía
cualquier gema.
+ global_html: Este webhook se ejecutaba antes cuando se subía
cualquier
+ gema.
gem_text: Este webhook se ejecutaba antes cuando se subía %{gem}.
gem_html: Este webhook se ejecutaba antes cuando se subía
%{gem}.
web_hook_disabled:
@@ -338,9 +380,12 @@ es:
La última que se ejecutó con éxito fue en %{last_success}
y desde entonces ha fallado %{failures_since_last_success} veces.
Puedes borrar este webhook usando el comando %{delete_command}
.
global_text: Este webhook se ejecutaba antes cuando se subía cualquier gema.
- global_html: Este webhook se ejecutaba antes cuando se subía
cualquier gema.
+ global_html: Este webhook se ejecutaba antes cuando se subía
cualquier
+ gema.
gem_text: Este webhook se ejecutaba antes cuando se subía %{gem}.
gem_html: Este webhook se ejecutaba antes cuando se subía
%{gem}.
+ gem_trusted_publisher_added:
+ title:
news:
show:
title: Nuevos lanzamientos — Todas las Gemas
@@ -351,11 +396,12 @@ es:
pages:
about:
contributors_amount: "%{count} Rubystas"
- downloads_amount: "millones de descargas de gemas"
- checkout_code: "por favor echa un ojo al código"
- mit_licensed: "MIT"
+ downloads_amount: millones de descargas de gemas
+ checkout_code: por favor echa un ojo al código
+ mit_licensed: MIT
logo_header: "¿Buscas nuestro logo?"
- logo_details: Usa el botón de descarga y obtendrás tres archivos .PNG y un .SVG del logo de RubyGems.
+ logo_details: Usa el botón de descarga y obtendrás tres archivos .PNG y un .SVG
+ del logo de RubyGems.
founding_html: El proyecto comenzó en abril del 2009 por %{founder}, y desde
entonces ha crecido para incluir las contribuciones de %{contributors} y %{downloads}.
A partir de RubyGems 1.3.6 el sitio se renombró de Gemcutter a %{title} para
@@ -398,26 +444,41 @@ es:
new:
submit: Restablecer contraseña
title: Cambiar tu contraseña
- will_email_notice: Te enviaremos el enlace para cambiar tu contraseña por correo electrónico.
+ will_email_notice: Te enviaremos el enlace para cambiar tu contraseña por correo
+ electrónico.
multifactor_auths:
incorrect_otp: Tu código OTP no es correcto.
session_expired: Ha expirado tu sesión en la página de acceso.
- require_totp_disabled: La autenticación de múltiples factores basada en OTP ya está activa. Para reconfigurarla debes primero eliminarla.
- require_mfa_enabled: No se ha activado la autenticación de múltiples factores. Primero tienes que activarla.
- require_totp_enabled: No tienes aplicación de autenticación activa. Debes activarla primero.
- require_webauthn_enabled: No tienes ningún dispositivo de seguridad activado. Primero debes asociar un dispositivo a tu cuenta.
- setup_required_html: Por la seguridad de tu cuenta y de tus gemas se te requiere activar la autenticación de múltiples factores.
- Lee por favor el
artículo en nuestro blog para saber más detalles.
- setup_recommended: Por la seguridad de tu cuenta y de tus gemas te animamos a configurar la autenticación de múltiples factores. En el futuro será obligatorio tener AMF activada en tu cuenta.
- strong_mfa_level_required_html: Por la seguridad de tu cuenta y de tus gemas es necesario que cambies el nivel de AMF a "Interfaz de usuario y firma de gemas" o "Interfaz de usuario y API".
- Lee por favor el
artículo en nuestro blog para saber más detalles.
- strong_mfa_level_recommended: Por la seguridad de tu cuenta y de tus gemas te recomendamos que cambies el nivel de AMF a "Interfaz de usuario y firma de gemas" o "Interfaz de usuario y API". En el futuro será obligatorio tener AMF configurada en alguno de esos niveles.
- setup_webauthn_html: 🎉 ¡Ahora soportamos dispositivos de seguridad! Aumenta la seguridad de tu cuenta
configurando un nuevo dispositivo.
+ require_totp_disabled: La autenticación de múltiples factores basada en OTP ya
+ está activa. Para reconfigurarla debes primero eliminarla.
+ require_mfa_enabled: No se ha activado la autenticación de múltiples factores.
+ Primero tienes que activarla.
+ require_totp_enabled: No tienes aplicación de autenticación activa. Debes activarla
+ primero.
+ require_webauthn_enabled: No tienes ningún dispositivo de seguridad activado.
+ Primero debes asociar un dispositivo a tu cuenta.
+ setup_required_html: Por la seguridad de tu cuenta y de tus gemas se te requiere
+ activar la autenticación de múltiples factores. Lee por favor el
artículo
+ en nuestro blog para saber más detalles.
+ setup_recommended: Por la seguridad de tu cuenta y de tus gemas te animamos a
+ configurar la autenticación de múltiples factores. En el futuro será obligatorio
+ tener AMF activada en tu cuenta.
+ strong_mfa_level_required_html: Por la seguridad de tu cuenta y de tus gemas es
+ necesario que cambies el nivel de AMF a "Interfaz de usuario y firma de gemas"
+ o "Interfaz de usuario y API". Lee por favor el
artículo
+ en nuestro blog para saber más detalles.
+ strong_mfa_level_recommended: Por la seguridad de tu cuenta y de tus gemas te
+ recomendamos que cambies el nivel de AMF a "Interfaz de usuario y firma de gemas"
+ o "Interfaz de usuario y API". En el futuro será obligatorio tener AMF configurada
+ en alguno de esos niveles.
+ setup_webauthn_html: "\U0001F389 ¡Ahora soportamos dispositivos de seguridad!
+ Aumenta la seguridad de tu cuenta
configurando
+ un nuevo dispositivo."
new:
title: Activando autenticación de múltiples factores
scan_prompt: Por favor escanea el código QR con tu aplicación de autenticación.
- Si no puedes escanear el código, agrega manualmente la información siguiente a tu
- aplicación.
+ Si no puedes escanear el código, agrega manualmente la información siguiente
+ a tu aplicación.
otp_prompt: Escribe el código de la aplicación de autenticación para continuar.
confirm: He mantenido seguros mis códigos de recuperación.
enable: Activar
@@ -425,8 +486,8 @@ es:
key: 'Clave: %{key}'
time_based: 'Basado en tiempo: Sí'
create:
- qrcode_expired: El código QR y la clave han vencido. Por favor intenta otra vez
- registrar un nuevo dispositivo.
+ qrcode_expired: El código QR y la clave han vencido. Por favor intenta otra
+ vez registrar un nuevo dispositivo.
success: Has activado con éxito la autenticación de múltiples factores.
recovery:
copied: "[ copiado ]"
@@ -434,7 +495,10 @@ es:
title: Códigos de recuperación
copy: "[ copiar ]"
saved: Declaro haber guardado mis códigos de recuperación.
- note_html: "Por favor
copia y guarda estos códigos de recuperación. Puedes usar estos códigos para acceder y restablecer tu autenticación de múltiples factores si pierdes tu dispositivo. Cada código se puede usar una sola vez."
+ note_html: Por favor
copia y guarda
+ estos códigos de recuperación. Puedes usar estos códigos para acceder y restablecer
+ tu autenticación de múltiples factores si pierdes tu dispositivo. Cada código
+ se puede usar una sola vez.
already_generated: Ya deberías haber guardado tus códigos de recuperación.
destroy:
success: Has desactivado exitosamente la autenticación de múltiples factores.
@@ -442,22 +506,26 @@ es:
invalid_level: Nivel de AMF inválido.
success: Has actualizado exitosamente la autenticación de múltiples factores.
prompt:
- webauthn_credential_note: Autentícate con un dispositivo de seguridad como Touch Id, YubiKey, etc.
+ webauthn_credential_note: Autentícate con un dispositivo de seguridad como Touch
+ Id, YubiKey, etc.
sign_in_with_webauthn_credential: Autenticar con dispositivo de seguridad
otp_code: Código OTP
otp_or_recovery: OTP o código de recuperación
recovery_code: Código de recuperación
- recovery_code_html: 'Puedes utilizar un
código de recuperación válido si has perdido el acceso a tu dispositivo de seguridad o de autenticación de múltiples factores.'
+ recovery_code_html: Puedes utilizar un
código de recuperación válido si has perdido el acceso
+ a tu dispositivo de seguridad o de autenticación de múltiples factores.
security_device: Dispositivo de seguridad
verify_code: Verificar código
notifiers:
update:
- success: Has actualizado exitosamente la configuración de tus notificaciones por correo.
+ success: Has actualizado exitosamente la configuración de tus notificaciones
+ por correo.
show:
- info:
- Para ayudar a detectar cambios no autorizados en gemas o en propietarios, te enviamos un correo electrónico
- cada vez que se sube o se elimina una versión de cualquiera de tus gemas o se le añade un nuevo propietario.
- Recibiendo y leyendo esos mensajes ayudas al ecosistema de Ruby.
+ info: Para ayudar a detectar cambios no autorizados en gemas o en propietarios,
+ te enviamos un correo electrónico cada vez que se sube o se elimina una versión
+ de cualquiera de tus gemas o se le añade un nuevo propietario. Recibiendo
+ y leyendo esos mensajes ayudas al ecosistema de Ruby.
'on': Activado
'off': Desactivado
recommended: recomendado
@@ -467,7 +535,8 @@ es:
owner_request_heading: Notificaciones de solicitud de propietarios
push_heading: Notificaciones Push
webauthn_verifications:
- expired_or_already_used: El token del enlace utilizado ha expirado o ya ha sido utilizado.
+ expired_or_already_used: El token del enlace utilizado ha expirado o ya ha sido
+ utilizado.
no_port: No se especifica el puerto. Por favor inténtalo de nuevo.
pending: La autenticación del dispositivo de seguridad está pendiente todavía.
prompt:
@@ -483,11 +552,12 @@ es:
close_browser: Por favor cierra este navegador e inténtalo de nuevo.
owners:
confirm:
- confirmed_email: "Has sido añadido/a como propietario de la gema %{gem}"
- token_expired: El token de confirmación ha expirado. Por favor intenta reenviar el token desde la página de la gema.
+ confirmed_email: Has sido añadido/a como propietario de la gema %{gem}
+ token_expired: El token de confirmación ha expirado. Por favor intenta reenviar
+ el token desde la página de la gema.
index:
add_owner: AÑADIR PROPIETARIO
- name: "PROPIETARIO/A"
+ name: PROPIETARIO/A
mfa: ESTADO DE AMF
status: ESTADO
confirmed_at: CONFIRMADO
@@ -498,21 +568,25 @@ es:
info: añadir o eliminar propietarios
confirmed: Confirmado
pending: Pendiente
- confirm_remove: ¿Seguro que quieres eliminar a este usuario de los propietarios?
+ confirm_remove: "¿Seguro que quieres eliminar a este usuario de los propietarios?"
resend_confirmation:
resent_notice: Se ha reenviado un mensaje de confirmación a tu correo electrónico
create:
- success_notice: "Se ha añadido a %{handle} como propietario sin confirmar. Su acceso como propietario se activará cuando haga click en el mensaje de confirmación que se le ha enviado a su correo"
+ success_notice: Se ha añadido a %{handle} como propietario sin confirmar. Su
+ acceso como propietario se activará cuando haga click en el mensaje de confirmación
+ que se le ha enviado a su correo
destroy:
removed_notice: "%{owner_name} eliminado con éxito de la lista de propietarios"
failed_notice: No se puede eliminar al único propietario de una gema
- mfa_required: La gema tiene activado el requerimiento de AMF, configura AMF en tu cuenta por favor.
+ mfa_required: La gema tiene activado el requerimiento de AMF, configura AMF en
+ tu cuenta por favor.
settings:
edit:
title: Editar configuración
webauthn_credentials: Dispositivo de seguridad
no_webauthn_credentials: No tienes dispositivos de seguridad
- webauthn_credential_note: Un dispositivo de seguridad puede ser cualquier dispositivo que cumpla el estándar FIDO2 como las llaves biométrica y de seguridad.
+ webauthn_credential_note: Un dispositivo de seguridad puede ser cualquier dispositivo
+ que cumpla el estándar FIDO2 como las llaves biométrica y de seguridad.
otp_code: Código OTP o código de recuperación
api_access:
confirm_reset: "¿Seguro? Este cambio no puede deshacerse."
@@ -529,10 +603,16 @@ es:
mfa:
multifactor_auth: Autenticación de múltiples factores
otp: Aplicación de autenticación
- disabled_html: No has activado todavía la autenticación de múltiples factores basada en OTP. Por favor lee la
guía sobre AMF para informarte sobre los distintos niveles de AMF.
+ disabled_html: No has activado todavía la autenticación de múltiples factores
+ basada en OTP. Por favor lee la
guía
+ sobre AMF para informarte sobre los distintos niveles de AMF.
go_settings: Registrar un nuevo dispositivo
- level_html: Has activado la autenticación de múltiples factores. Pincha en "Actualizar" para modificar el nivel de AMF. Por favor lee la
guía sobre AMF para informarte sobre los distintos niveles de AMF.
- enabled_note: Has activado la autenticación de múltiples factores. Para desactivarla usa tu OTP o uno de tus códigos de recuperación activos.
+ level_html: Has activado la autenticación de múltiples factores. Pincha en
+ "Actualizar" para modificar el nivel de AMF. Por favor lee la
guía
+ sobre AMF para informarte sobre los distintos niveles de AMF.
+ enabled_note: Has activado la autenticación de múltiples factores. Para desactivarla
+ usa tu OTP o uno de tus códigos de recuperación activos.
update: Actualizar
disable: Desactivar
enabled: Activado
@@ -545,17 +625,25 @@ es:
ui_and_gem_signin: Interfaz de Usuario y firma de gemas
profiles:
adoptions:
- no_ownership_calls: No has creado llamadas a ser propietario para ninguna de tus gemas
+ no_ownership_calls: No has creado llamadas a ser propietario para ninguna de
+ tus gemas
no_ownership_requests: No has creado ninguna petición para ser propietario
title: Adopción
- subtitle_html: Pide nuevos responsables de mantenimiento o solicita propietarios
(leer más)
+ subtitle_html: Pide nuevos responsables de mantenimiento o solicita propietarios
+
(leer
+ más)
edit:
change_avatar: Cambiar avatar
- disabled_avatar_html: Se usa un avatar por defecto debido a la configuración de privacidad del email. Para usar un
Gravatar personalizado activa la opción 'Mostrar correo electrónico en perfil público'. Ten en cuenta que esto hará público tu correo."
- email_awaiting_confirmation: Por favor confirma tu nueva dirección de correo %{unconfirmed_email}
+ disabled_avatar_html: Se usa un avatar por defecto debido a la configuración
+ de privacidad del email. Para usar un
Gravatar
+ personalizado activa la opción 'Mostrar correo electrónico en perfil público'.
+ Ten en cuenta que esto hará público tu correo."
+ email_awaiting_confirmation: Por favor confirma tu nueva dirección de correo
+ %{unconfirmed_email}
enter_password: Por favor introduce tu contraseña
optional_full_name: Opcional. Será mostrado en tu perfil público
- optional_twitter_username: Usuario de X opcional. Será mostrado en tu perfil público
+ optional_twitter_username: Usuario de X opcional. Será mostrado en tu perfil
+ público
title: Editar perfil
delete:
delete: Eliminar
@@ -614,17 +702,21 @@ es:
ownership: Propietarios
oidc:
api_key_role:
- name: "OIDC: %{name}"
- new: "OIDC: Crear"
+ name: 'OIDC: %{name}'
+ new: 'OIDC: Crear'
+ trusted_publishers:
reserved:
reserved_namespace: Este namespace está reservado por rubygems.org.
dependencies:
header: dependencias de %{title}
gem_members:
authors_header: Autores
- self_no_mfa_warning_html: Considera por favor
activar la autenticación de múltiples factores (AMF) para mantener tu cuenta segura.
- not_using_mfa_warning_show: "* Algunos propietarios no están usando AMF. Haga click para ver la lista completa."
- not_using_mfa_warning_hide: "* Los siguientes propietarios no están usando AMF. Haga click para ocultar."
+ self_no_mfa_warning_html: Considera por favor
activar
+ la autenticación de múltiples factores (AMF) para mantener tu cuenta segura.
+ not_using_mfa_warning_show: "* Algunos propietarios no están usando AMF. Haga
+ click para ver la lista completa."
+ not_using_mfa_warning_hide: "* Los siguientes propietarios no están usando AMF.
+ Haga click para ocultar."
owners_header: Propietarios
pushed_by: Subida por
using_mfa_info: "* Todos los propietarios están usando AMF."
@@ -633,7 +725,7 @@ es:
signature_period: Periodo de validez de la firma
expired: Expirado
version_navigation:
- previous_version: ← Versión anterior
+ previous_version: "← Versión anterior"
next_version: Siguiente versión →
index:
downloads: Descargas
@@ -671,7 +763,7 @@ es:
reverse_dependencies:
index:
title: Dependencias inversas para %{name}
- subtitle: "La última versión de las siguientes gemas requieren %{name}"
+ subtitle: La última versión de las siguientes gemas requieren %{name}
no_reverse_dependencies: Esta gema no tiene dependencias inversas
search:
search_reverse_dependencies_html: Buscar dependencias inversas…
@@ -699,7 +791,8 @@ es:
confirm: Confirmar
notice: Por favor confirma tu contraseña para continuar.
create:
- account_blocked: Tu cuenta ha sido bloqueada por el equipo de rubygems. Para recuperar tu cuenta envía un mensaje a support@rubygems.org, por favor.
+ account_blocked: Tu cuenta ha sido bloqueada por el equipo de rubygems. Para
+ recuperar tu cuenta envía un mensaje a support@rubygems.org, por favor.
stats:
index:
title: Estadísticas
@@ -709,7 +802,8 @@ es:
total_users: Usuarios totales
users:
create:
- email_sent: Se ha enviado un correo de confirmación a tu dirección de correo electrónico.
+ email_sent: Se ha enviado un correo de confirmación a tu dirección de correo
+ electrónico.
new:
have_account: "¿Ya tienes una cuenta?"
versions:
@@ -717,16 +811,23 @@ es:
not_hosted_notice: Esta gema no está alojada actualmente en RubyGems.org.
title: Todas las versiones de %{name}
versions_since: "%{count} versiones desde %{since}"
- imported_gem_version_notice: "Esta versión de la gema se importó a RubyGems.org el %{import_date}. La fecha que se muestra fue especificada por el autor en el archivo gemspec."
+ imported_gem_version_notice: Esta versión de la gema se importó a RubyGems.org
+ el %{import_date}. La fecha que se muestra fue especificada por el autor en
+ el archivo gemspec.
version:
yanked: borrada
adoptions:
index:
title: Adopciones
- subtitle_owner_html: Solicita nuevos responsables de mantenimiento para %{gem}
(leer más)
- subtitle_user_html: Solicita ser propietario de %{gem}
(leer más)
+ subtitle_owner_html: Solicita nuevos responsables de mantenimiento para %{gem}
+
(leer
+ más)
+ subtitle_user_html: Solicita ser propietario de %{gem}
(leer
+ más)
ownership_calls: Solicitud de propietarios
- no_ownership_calls: No hay convocatorias de propietarios para %{gem}. Los dueños de la gema no están buscando nuevos responsables de mantenimiento.
+ no_ownership_calls: No hay convocatorias de propietarios para %{gem}. Los dueños
+ de la gema no están buscando nuevos responsables de mantenimiento.
ownership_calls:
update:
success_notice: Convocatoria para propietarios de %{gem} cerrada.
@@ -734,14 +835,17 @@ es:
success_notice: Creada convocatoria para propietarios de %{gem}.
index:
title: Se buscan responsables de mantenimiento
- subtitle_html: Gemas que buscan nuevos responsables de mantenimiento
(leer más)
+ subtitle_html: Gemas que buscan nuevos responsables de mantenimiento
(leer
+ más)
share_requirements: Por favor especifica en que areas necesitas ayuda
- note_for_applicants: "Nota para candidatos:"
+ note_for_applicants: 'Nota para candidatos:'
created_by: Creado por
details: Detalles
apply: Proponte
close: Cerrar
- markup_supported_html:
Etiquetas Rdoc soportadas
+ markup_supported_html:
Etiquetas
+ Rdoc soportadas
create_call: Crear convocatoria para propietarios
ownership_requests:
create:
@@ -752,28 +856,33 @@ es:
close:
success_notice: Se han cerrado todas las candidaturas a propietario de %{gem}.
ownership_requests: Candidaturas a propietario
- note_for_owners: "Nota para propietarios:"
+ note_for_owners: 'Nota para propietarios:'
your_ownership_requests: Tus candidaturas a propietario
close_all: Cerrar todas
approve: Aprobar
gems_published: Gemas publicadas
created_at: Creado el
- no_ownership_requests: Las peticiones para unirse a tu proyecto aparecerán aquí. Todavia no hay candidaturas a propietario para %{gem}.
+ no_ownership_requests: Las peticiones para unirse a tu proyecto aparecerán aquí.
+ Todavia no hay candidaturas a propietario para %{gem}.
create_req: Crea una candidatura a propietario
- signin_to_create_html: Por favor
accede para crear una candidatura a propietario.
+ signin_to_create_html: Por favor
accede
+ para crear una candidatura a propietario.
webauthn_credentials:
callback:
success: Has dado de alta con éxito un dispositivo de seguridad.
recovery:
continue: Continuar
title: Has añadido con éxito un dispositivo de seguridad
- notice_html: 'Por favor
copia y guarda estos códigos de recuperación. Puedes utilizar estos códigos para acceder si pierdes tu dispositivo de seguridad. Cada código solo se puede usar una vez.'
+ notice_html: Por favor
copia y guarda
+ estos códigos de recuperación. Puedes utilizar estos códigos para acceder
+ si pierdes tu dispositivo de seguridad. Cada código solo se puede usar una
+ vez.
copied: "[ copiado ]"
copy: "[ copiar ]"
saved: Declaro haber guardado mis códigos de recuperación.
webauthn_credential:
- confirm_delete: "Credencial borrada"
- delete_failed: "No se pudo borrar la credencial"
+ confirm_delete: Credencial borrada
+ delete_failed: No se pudo borrar la credencial
delete: Borrar
confirm: "¿Seguro que quieres borrar esta credencial?"
saved: Dispositivo de seguridad creado con éxito
@@ -787,61 +896,110 @@ es:
api_key_roles: Roles de clave API OIDC
new_role: Crear rol de clave API
show:
- api_key_role_name: "Rol de clave API %{name}"
- automate_gh_actions_publishing: "Automatizar publicación de gemas con GitHub Actions"
- view_provider: "Ver proveedor %{issuer}"
- edit_role: "Editar rol de clave API"
- delete_role: "Borrar rol de clave API"
+ api_key_role_name: Rol de clave API %{name}
+ automate_gh_actions_publishing: Automatizar publicación de gemas con GitHub
+ Actions
+ view_provider: Ver proveedor %{issuer}
+ edit_role: Editar rol de clave API
+ delete_role: Borrar rol de clave API
confirm_delete: "¿Seguro que quieres borrar este rol?"
- deleted_at_html: "Este rol se borró hace %{time_html} y ya no puede usarse."
+ deleted_at_html: Este rol se borró hace %{time_html} y ya no puede usarse.
edit:
- edit_role: "Editar rol de clave API"
+ edit_role: Editar rol de clave API
git_hub_actions_workflow:
- title: "OIDC GitHub Actions Workflow para subir gema"
- configured_for_html: "Este rol de clave API OIDC está configurado para permitir subir %{link_html} desde GitHub Actions."
- to_automate_html: "Para automatizar lanzar %{link_html} cuando se suba una nueva etiqueta, añade el siguiente workflow a tu repositorio."
- not_github: "Este rol de clave API OIDC no está configurado para usar GitHub Actions."
- not_push: "Este rol de clave API OIDC no está configurado para permitir subir gemas."
+ title: OIDC GitHub Actions Workflow para subir gema
+ configured_for_html: Este rol de clave API OIDC está configurado para permitir
+ subir %{link_html} desde GitHub Actions.
+ to_automate_html: Para automatizar lanzar %{link_html} cuando se suba una
+ nueva etiqueta, añade el siguiente workflow a tu repositorio.
+ not_github: Este rol de clave API OIDC no está configurado para usar GitHub
+ Actions.
+ not_push: Este rol de clave API OIDC no está configurado para permitir subir
+ gemas.
copy_to_clipboard: Copiar al portapapeles
- copied: ¡Copiado!
+ copied: "¡Copiado!"
a_gem: una gema
- instructions_html: |
- Para lanzar una gema,
crea la nueva versión y genera una etiqueta nueva (usando
rake release:source_control_push
) a GitHub. El workflow empaquetará y subirá automáticamente la gema a RubyGems.org.
+ instructions_html: 'Para lanzar una gema,
crea la nueva versión y genera una etiqueta nueva (usando
+
rake release:source_control_push
) a GitHub. El workflow empaquetará
+ y subirá automáticamente la gema a RubyGems.org.
+
+ '
new:
- title: "Nuevo rol de clave API OIDC"
+ title: Nuevo rol de clave API OIDC
update:
- success: "Rol de clave API OIDC actualizado"
+ success: Rol de clave API OIDC actualizado
create:
- success: "Rol de clave API OIDC creado"
+ success: Rol de clave API OIDC creado
destroy:
- success: "Rol de clave API OIDC borrado"
+ success: Rol de clave API OIDC borrado
form:
- add_condition: "Añadir condición"
- remove_condition: "Eliminar condición!"
- add_statement: "Añadir declaración"
- remove_statement: "Eliminar declaración"
- deleted: "El rol se ha eliminado."
+ add_condition: Añadir condición
+ remove_condition: Eliminar condición!
+ add_statement: Añadir declaración
+ remove_statement: Eliminar declaración
+ deleted: El rol se ha eliminado.
providers:
index:
- title: "Proveedores de OIDC"
- description_html: "Estos son los proveedores de OIDC que están configurados para RubyGems.org.
Por favor, contacta con soporte si necesitas añadir otro proveedor OIDC."
+ title: Proveedores de OIDC
+ description_html: Estos son los proveedores de OIDC que están configurados
+ para RubyGems.org.
Por favor, contacta con soporte si necesitas añadir
+ otro proveedor OIDC.
show:
- title: "Proveedor de OIDC"
+ title: Proveedor de OIDC
id_tokens:
index:
- title: "Tokens OIDC ID"
+ title: Tokens OIDC ID
show:
- title: "Token OIDC ID"
+ title: Token OIDC ID
+ rubygem_trusted_publishers:
+ index:
+ title:
+ subtitle_owner_html:
+ delete:
+ create:
+ description_html:
+ destroy:
+ success:
+ create:
+ success:
+ new:
+ title:
+ subtitle_owner_html:
+ pending_trusted_publishers:
+ index:
+ title:
+ valid_for_html:
+ delete:
+ create:
+ description_html:
+ destroy:
+ success:
+ create:
+ success:
+ new:
+ title:
+ trusted_publisher:
+ unsupported_type:
+ github_actions:
+ repository_owner_help_html:
+ repository_name_help_html:
+ workflow_filename_help_html:
+ environment_help_html:
+ pending:
+ rubygem_name_help_html:
duration:
minutes:
other: "%{count} minutos"
- one: "1 minuto"
+ one: 1 minuto
hours:
other: "%{count} horas"
- one: "1 hora"
+ one: 1 hora
days:
other: "%{count} días"
- one: "1 día"
+ one: 1 día
seconds:
other: "%{count} segundos"
- one: "1 segundo"
+ one: 1 segundo
+ form:
+ optional:
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index 118c3fe286e..06dfcaeb775 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -68,6 +68,10 @@ fr:
api_key_role:
oidc/api_key_role:
api_key_permissions:
+ oidc/trusted_publisher/github_action:
+ repository_owner_id:
+ oidc/pending_trusted_publisher:
+ rubygem_name:
errors:
messages:
unpwn:
@@ -84,6 +88,14 @@ fr:
taken:
full_name:
taken:
+ oidc/rubygem_trusted_publisher:
+ attributes:
+ rubygem:
+ taken:
+ oidc/pending_trusted_publisher:
+ attributes:
+ rubygem_name:
+ unavailable:
models:
user:
activemodel:
@@ -337,6 +349,8 @@ fr:
global_html:
gem_text:
gem_html:
+ gem_trusted_publisher_added:
+ title:
news:
show:
title: Nouvelles Versions - Toutes les Gems
@@ -617,6 +631,7 @@ fr:
api_key_role:
name:
new:
+ trusted_publishers:
reserved:
reserved_namespace: Ce nom est réservé par rubygems.org.
dependencies:
@@ -833,6 +848,42 @@ fr:
title:
show:
title:
+ rubygem_trusted_publishers:
+ index:
+ title:
+ subtitle_owner_html:
+ delete:
+ create:
+ description_html:
+ destroy:
+ success:
+ create:
+ success:
+ new:
+ title:
+ subtitle_owner_html:
+ pending_trusted_publishers:
+ index:
+ title:
+ valid_for_html:
+ delete:
+ create:
+ description_html:
+ destroy:
+ success:
+ create:
+ success:
+ new:
+ title:
+ trusted_publisher:
+ unsupported_type:
+ github_actions:
+ repository_owner_help_html:
+ repository_name_help_html:
+ workflow_filename_help_html:
+ environment_help_html:
+ pending:
+ rubygem_name_help_html:
duration:
minutes:
other:
@@ -846,3 +897,5 @@ fr:
seconds:
other:
one:
+ form:
+ optional:
diff --git a/config/locales/ja.yml b/config/locales/ja.yml
index 84fdf96ce7d..56f812722ba 100644
--- a/config/locales/ja.yml
+++ b/config/locales/ja.yml
@@ -61,6 +61,10 @@ ja:
api_key_role:
oidc/api_key_role:
api_key_permissions:
+ oidc/trusted_publisher/github_action:
+ repository_owner_id:
+ oidc/pending_trusted_publisher:
+ rubygem_name:
errors:
messages:
unpwn: 過去にデータ侵害を受けたためお使いになれません
@@ -77,6 +81,14 @@ ja:
taken: "%{value}は既に存在します"
full_name:
taken: "%{value}は既に存在します"
+ oidc/rubygem_trusted_publisher:
+ attributes:
+ rubygem:
+ taken:
+ oidc/pending_trusted_publisher:
+ attributes:
+ rubygem_name:
+ unavailable:
models:
user: ユーザー
activemodel:
@@ -331,6 +343,8 @@ ja:
global_html: このwebhookは以前
何らかのgemがプッシュされたときに呼ばれました。
gem_text: このwebhookは以前%{gem}がプッシュされたときに呼ばれました。
gem_html: このwebhookは以前
%{gem}がプッシュされたときに呼ばれました。
+ gem_trusted_publisher_added:
+ title:
news:
show:
title: 新しいリリース - 全てのgem
@@ -341,7 +355,7 @@ ja:
pages:
about:
contributors_amount: "%{count}人以上のRubyist"
- downloads_amount: '何億回ものgemダウンロード'
+ downloads_amount: 何億回ものgemダウンロード
checkout_code:
mit_licensed:
logo_header:
@@ -583,6 +597,7 @@ ja:
api_key_role:
name:
new:
+ trusted_publishers:
reserved:
reserved_namespace: この名前空間はrubygems.orgにより予約されています。
dependencies:
@@ -792,6 +807,42 @@ ja:
title:
show:
title:
+ rubygem_trusted_publishers:
+ index:
+ title:
+ subtitle_owner_html:
+ delete:
+ create:
+ description_html:
+ destroy:
+ success:
+ create:
+ success:
+ new:
+ title:
+ subtitle_owner_html:
+ pending_trusted_publishers:
+ index:
+ title:
+ valid_for_html:
+ delete:
+ create:
+ description_html:
+ destroy:
+ success:
+ create:
+ success:
+ new:
+ title:
+ trusted_publisher:
+ unsupported_type:
+ github_actions:
+ repository_owner_help_html:
+ repository_name_help_html:
+ workflow_filename_help_html:
+ environment_help_html:
+ pending:
+ rubygem_name_help_html:
duration:
minutes:
other:
@@ -805,3 +856,5 @@ ja:
seconds:
other:
one:
+ form:
+ optional:
diff --git a/config/locales/nl.yml b/config/locales/nl.yml
index 2bedc1881ce..9251d3293fb 100644
--- a/config/locales/nl.yml
+++ b/config/locales/nl.yml
@@ -60,6 +60,10 @@ nl:
api_key_role:
oidc/api_key_role:
api_key_permissions:
+ oidc/trusted_publisher/github_action:
+ repository_owner_id:
+ oidc/pending_trusted_publisher:
+ rubygem_name:
errors:
messages:
unpwn:
@@ -76,6 +80,14 @@ nl:
taken:
full_name:
taken:
+ oidc/rubygem_trusted_publisher:
+ attributes:
+ rubygem:
+ taken:
+ oidc/pending_trusted_publisher:
+ attributes:
+ rubygem_name:
+ unavailable:
models:
user:
activemodel:
@@ -322,6 +334,8 @@ nl:
global_html:
gem_text:
gem_html:
+ gem_trusted_publisher_added:
+ title:
news:
show:
title:
@@ -584,6 +598,7 @@ nl:
api_key_role:
name:
new:
+ trusted_publishers:
reserved:
reserved_namespace:
dependencies:
@@ -787,6 +802,42 @@ nl:
title:
show:
title:
+ rubygem_trusted_publishers:
+ index:
+ title:
+ subtitle_owner_html:
+ delete:
+ create:
+ description_html:
+ destroy:
+ success:
+ create:
+ success:
+ new:
+ title:
+ subtitle_owner_html:
+ pending_trusted_publishers:
+ index:
+ title:
+ valid_for_html:
+ delete:
+ create:
+ description_html:
+ destroy:
+ success:
+ create:
+ success:
+ new:
+ title:
+ trusted_publisher:
+ unsupported_type:
+ github_actions:
+ repository_owner_help_html:
+ repository_name_help_html:
+ workflow_filename_help_html:
+ environment_help_html:
+ pending:
+ rubygem_name_help_html:
duration:
minutes:
other:
@@ -800,3 +851,5 @@ nl:
seconds:
other:
one:
+ form:
+ optional:
diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml
index b325ff3e11e..104b5feedc2 100644
--- a/config/locales/pt-BR.yml
+++ b/config/locales/pt-BR.yml
@@ -67,6 +67,10 @@ pt-BR:
api_key_role:
oidc/api_key_role:
api_key_permissions:
+ oidc/trusted_publisher/github_action:
+ repository_owner_id:
+ oidc/pending_trusted_publisher:
+ rubygem_name:
errors:
messages:
unpwn: já apareceu anteriormente em um vazamento de dados e não deve ser utilizada
@@ -83,6 +87,14 @@ pt-BR:
taken:
full_name:
taken:
+ oidc/rubygem_trusted_publisher:
+ attributes:
+ rubygem:
+ taken:
+ oidc/pending_trusted_publisher:
+ attributes:
+ rubygem_name:
+ unavailable:
models:
user: Usuário
activemodel:
@@ -334,6 +346,8 @@ pt-BR:
global_html:
gem_text:
gem_html:
+ gem_trusted_publisher_added:
+ title:
news:
show:
title: Novos Releases - Todas as Gems
@@ -595,6 +609,7 @@ pt-BR:
api_key_role:
name:
new:
+ trusted_publishers:
reserved:
reserved_namespace: This namespace is reserved by rubygems.org.
dependencies:
@@ -810,6 +825,42 @@ pt-BR:
title:
show:
title:
+ rubygem_trusted_publishers:
+ index:
+ title:
+ subtitle_owner_html:
+ delete:
+ create:
+ description_html:
+ destroy:
+ success:
+ create:
+ success:
+ new:
+ title:
+ subtitle_owner_html:
+ pending_trusted_publishers:
+ index:
+ title:
+ valid_for_html:
+ delete:
+ create:
+ description_html:
+ destroy:
+ success:
+ create:
+ success:
+ new:
+ title:
+ trusted_publisher:
+ unsupported_type:
+ github_actions:
+ repository_owner_help_html:
+ repository_name_help_html:
+ workflow_filename_help_html:
+ environment_help_html:
+ pending:
+ rubygem_name_help_html:
duration:
minutes:
other:
@@ -823,3 +874,5 @@ pt-BR:
seconds:
other:
one:
+ form:
+ optional:
diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml
index 567acc7ad11..377abc7a339 100644
--- a/config/locales/zh-CN.yml
+++ b/config/locales/zh-CN.yml
@@ -62,6 +62,10 @@ zh-CN:
api_key_role:
oidc/api_key_role:
api_key_permissions:
+ oidc/trusted_publisher/github_action:
+ repository_owner_id:
+ oidc/pending_trusted_publisher:
+ rubygem_name:
errors:
messages:
unpwn: 曾出现过数据泄露,不应该再使用
@@ -78,6 +82,14 @@ zh-CN:
taken:
full_name:
taken:
+ oidc/rubygem_trusted_publisher:
+ attributes:
+ rubygem:
+ taken:
+ oidc/pending_trusted_publisher:
+ attributes:
+ rubygem_name:
+ unavailable:
models:
user: 用户
activemodel:
@@ -336,6 +348,8 @@ zh-CN:
gem_text: 这个 webhook 在 %{gem} 被推送时被调用。
gem_html: 这个 webhook 在
%{gem}
被推送时被调用。
+ gem_trusted_publisher_added:
+ title:
news:
show:
title: 新的发布 — 所有 Gem
@@ -591,6 +605,7 @@ zh-CN:
api_key_role:
name:
new:
+ trusted_publishers:
reserved:
reserved_namespace: 该命名空间由 RubyGems.org 保留。
dependencies:
@@ -800,6 +815,42 @@ zh-CN:
title:
show:
title:
+ rubygem_trusted_publishers:
+ index:
+ title:
+ subtitle_owner_html:
+ delete:
+ create:
+ description_html:
+ destroy:
+ success:
+ create:
+ success:
+ new:
+ title:
+ subtitle_owner_html:
+ pending_trusted_publishers:
+ index:
+ title:
+ valid_for_html:
+ delete:
+ create:
+ description_html:
+ destroy:
+ success:
+ create:
+ success:
+ new:
+ title:
+ trusted_publisher:
+ unsupported_type:
+ github_actions:
+ repository_owner_help_html:
+ repository_name_help_html:
+ workflow_filename_help_html:
+ environment_help_html:
+ pending:
+ rubygem_name_help_html:
duration:
minutes:
other:
@@ -813,3 +864,5 @@ zh-CN:
seconds:
other:
one:
+ form:
+ optional:
diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml
index 2418a316566..2199807cae3 100644
--- a/config/locales/zh-TW.yml
+++ b/config/locales/zh-TW.yml
@@ -57,6 +57,10 @@ zh-TW:
api_key_role:
oidc/api_key_role:
api_key_permissions:
+ oidc/trusted_publisher/github_action:
+ repository_owner_id:
+ oidc/pending_trusted_publisher:
+ rubygem_name:
errors:
messages:
unpwn:
@@ -73,6 +77,14 @@ zh-TW:
taken:
full_name:
taken:
+ oidc/rubygem_trusted_publisher:
+ attributes:
+ rubygem:
+ taken:
+ oidc/pending_trusted_publisher:
+ attributes:
+ rubygem_name:
+ unavailable:
models:
user:
activemodel:
@@ -316,6 +328,8 @@ zh-TW:
global_html:
gem_text:
gem_html:
+ gem_trusted_publisher_added:
+ title:
news:
show:
title: 最新發佈
@@ -567,6 +581,7 @@ zh-TW:
api_key_role:
name:
new:
+ trusted_publishers:
reserved:
reserved_namespace:
dependencies:
@@ -770,6 +785,42 @@ zh-TW:
title:
show:
title:
+ rubygem_trusted_publishers:
+ index:
+ title:
+ subtitle_owner_html:
+ delete:
+ create:
+ description_html:
+ destroy:
+ success:
+ create:
+ success:
+ new:
+ title:
+ subtitle_owner_html:
+ pending_trusted_publishers:
+ index:
+ title:
+ valid_for_html:
+ delete:
+ create:
+ description_html:
+ destroy:
+ success:
+ create:
+ success:
+ new:
+ title:
+ trusted_publisher:
+ unsupported_type:
+ github_actions:
+ repository_owner_help_html:
+ repository_name_help_html:
+ workflow_filename_help_html:
+ environment_help_html:
+ pending:
+ rubygem_name_help_html:
duration:
minutes:
other:
@@ -783,3 +834,5 @@ zh-TW:
seconds:
other:
one:
+ form:
+ optional:
diff --git a/config/routes.rb b/config/routes.rb
index d6c2d56e9c7..24a6c63a690 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -113,6 +113,7 @@
resources :timeframe_versions, only: :index
namespace :oidc do
+ post 'trusted_publisher/exchange_token'
resources :api_key_roles, only: %i[index show], param: :token, format: 'json', defaults: { format: :json } do
member do
post :assume_role
@@ -182,6 +183,7 @@
resources :api_key_roles, param: :token, only: %i[show], constraints: { format: :json }
resources :id_tokens, only: %i[index show]
resources :providers, only: %i[index show]
+ resources :pending_trusted_publishers, except: %i[show edit update]
end
end
resources :stats, only: :index
@@ -214,6 +216,7 @@
patch 'close_all', to: 'ownership_requests#close_all', as: :close_all, on: :collection
end
resources :adoptions, only: %i[index]
+ resources :trusted_publishers, controller: 'oidc/rubygem_trusted_publishers', only: %i[index create destroy new]
end
resources :ownership_calls, only: :index
diff --git a/db/migrate/20231027190405_create_oidc_trusted_publisher_github_actions.rb b/db/migrate/20231027190405_create_oidc_trusted_publisher_github_actions.rb
new file mode 100644
index 00000000000..61109730267
--- /dev/null
+++ b/db/migrate/20231027190405_create_oidc_trusted_publisher_github_actions.rb
@@ -0,0 +1,17 @@
+class CreateOIDCTrustedPublisherGitHubActions < ActiveRecord::Migration[7.0]
+ def change
+ create_table :oidc_trusted_publisher_github_actions do |t|
+ t.string :repository_owner, null: false
+ t.string :repository_name, null: false
+ t.string :repository_owner_id, null: false
+ t.string :workflow_filename, null: false
+ t.string :environment, null: true
+
+ t.timestamps
+ end
+
+ add_index :oidc_trusted_publisher_github_actions,
+ [:repository_owner, :repository_name, :repository_owner_id, :workflow_filename, :environment],
+ unique: true, name: "index_oidc_trusted_publisher_github_actions_claims"
+ end
+end
diff --git a/db/migrate/20231027191446_create_oidc_rubygem_trusted_publishers.rb b/db/migrate/20231027191446_create_oidc_rubygem_trusted_publishers.rb
new file mode 100644
index 00000000000..1a9f2506b25
--- /dev/null
+++ b/db/migrate/20231027191446_create_oidc_rubygem_trusted_publishers.rb
@@ -0,0 +1,14 @@
+class CreateOIDCRubygemTrustedPublishers < ActiveRecord::Migration[7.0]
+ def change
+ create_table :oidc_rubygem_trusted_publishers do |t|
+ t.references :rubygem, null: false, foreign_key: true
+ t.references :trusted_publisher, polymorphic: true, null: false
+
+ t.timestamps
+ end
+
+ add_index :oidc_rubygem_trusted_publishers,
+ [:rubygem_id, :trusted_publisher_id, :trusted_publisher_type],
+ unique: true, name: "index_oidc_rubygem_trusted_publishers_unique"
+ end
+end
diff --git a/db/migrate/20231108205146_create_oidc_pending_trusted_publishers.rb b/db/migrate/20231108205146_create_oidc_pending_trusted_publishers.rb
new file mode 100644
index 00000000000..903524f64e3
--- /dev/null
+++ b/db/migrate/20231108205146_create_oidc_pending_trusted_publishers.rb
@@ -0,0 +1,12 @@
+class CreateOIDCPendingTrustedPublishers < ActiveRecord::Migration[7.0]
+ def change
+ create_table :oidc_pending_trusted_publishers do |t|
+ t.string :rubygem_name
+ t.references :user, null: false, foreign_key: true
+ t.references :trusted_publisher, null: false, polymorphic: true
+ t.timestamp :expires_at, null: false
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index e280f51d425..9166a238a6e 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -284,6 +284,18 @@
t.index ["oidc_api_key_role_id"], name: "index_oidc_id_tokens_on_oidc_api_key_role_id"
end
+ create_table "oidc_pending_trusted_publishers", force: :cascade do |t|
+ t.string "rubygem_name"
+ t.bigint "user_id", null: false
+ t.string "trusted_publisher_type", null: false
+ t.bigint "trusted_publisher_id", null: false
+ t.datetime "expires_at", precision: nil, null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["trusted_publisher_type", "trusted_publisher_id"], name: "index_oidc_pending_trusted_publishers_on_trusted_publisher"
+ t.index ["user_id"], name: "index_oidc_pending_trusted_publishers_on_user_id"
+ end
+
create_table "oidc_providers", force: :cascade do |t|
t.text "issuer"
t.jsonb "configuration"
@@ -293,6 +305,28 @@
t.index ["issuer"], name: "index_oidc_providers_on_issuer", unique: true
end
+ create_table "oidc_rubygem_trusted_publishers", force: :cascade do |t|
+ t.bigint "rubygem_id", null: false
+ t.string "trusted_publisher_type", null: false
+ t.bigint "trusted_publisher_id", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["rubygem_id", "trusted_publisher_id", "trusted_publisher_type"], name: "index_oidc_rubygem_trusted_publishers_unique", unique: true
+ t.index ["rubygem_id"], name: "index_oidc_rubygem_trusted_publishers_on_rubygem_id"
+ t.index ["trusted_publisher_type", "trusted_publisher_id"], name: "index_oidc_rubygem_trusted_publishers_on_trusted_publisher"
+ end
+
+ create_table "oidc_trusted_publisher_github_actions", force: :cascade do |t|
+ t.string "repository_owner", null: false
+ t.string "repository_name", null: false
+ t.string "repository_owner_id", null: false
+ t.string "workflow_filename", null: false
+ t.string "environment"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["repository_owner", "repository_name", "repository_owner_id", "workflow_filename", "environment"], name: "index_oidc_trusted_publisher_github_actions_claims", unique: true
+ end
+
create_table "ownership_calls", force: :cascade do |t|
t.bigint "rubygem_id"
t.bigint "user_id"
@@ -495,6 +529,8 @@
add_foreign_key "oidc_api_key_roles", "users"
add_foreign_key "oidc_id_tokens", "api_keys"
add_foreign_key "oidc_id_tokens", "oidc_api_key_roles"
+ add_foreign_key "oidc_pending_trusted_publishers", "users"
+ add_foreign_key "oidc_rubygem_trusted_publishers", "rubygems"
add_foreign_key "ownerships", "users", on_delete: :cascade
add_foreign_key "versions", "api_keys", column: "pusher_api_key_id"
add_foreign_key "webauthn_credentials", "users"
diff --git a/db/seeds.rb b/db/seeds.rb
index 5066bec2218..a141704fc2d 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -259,6 +259,37 @@
last_verified_at: 10.years.since,
).find_or_create_by!(uri: "https://example.com/rubygem0/code")
+trusted_publisher = OIDC::TrustedPublisher::GitHubAction.find_or_create_by!(
+ repository_owner: "example",
+ repository_name: "rubygem0",
+ repository_owner_id: "1234567890",
+ workflow_filename: "push_gem.yml",
+ environment: nil
+)
+trusted_publisher.rubygem_trusted_publishers.find_or_create_by!(rubygem: rubygem0).trusted_publisher.api_keys.find_or_create_by!(
+ name: "GitHub Actions something",
+ hashed_key: "securehashedkey-tp",
+ push_rubygem: true,
+).pushed_versions.create_with(indexed: true).find_or_create_by!(
+ rubygem: rubygem0, number: "0.1.0", platform: "ruby", gem_platform: "ruby"
+)
+trusted_publisher.rubygem_trusted_publishers.find_or_create_by!(rubygem: rubygem1)
+
+OIDC::TrustedPublisher::GitHubAction.find_or_create_by!(
+ repository_owner: "example",
+ repository_name: "rubygem0",
+ repository_owner_id: "1234567890",
+ workflow_filename: "push_gem2.yml",
+ environment: "deploy"
+).rubygem_trusted_publishers.find_or_create_by!(rubygem: rubygem0)
+
+author.oidc_pending_trusted_publishers.create_with(
+ expires_at: 100.years.from_now
+).find_or_create_by!(
+ trusted_publisher: trusted_publisher,
+ rubygem_name: "pending-trusted-publisher-rubygem"
+)
+
puts <<~MESSAGE # rubocop:disable Rails/Output
Four users were created, you can login with following combinations:
- email: #{author.email}, password: #{password} -> gem author owning few example gems
diff --git a/test/factories/oidc/pending_trusted_publishers.rb b/test/factories/oidc/pending_trusted_publishers.rb
new file mode 100644
index 00000000000..02ba468adcf
--- /dev/null
+++ b/test/factories/oidc/pending_trusted_publishers.rb
@@ -0,0 +1,8 @@
+FactoryBot.define do
+ factory :oidc_pending_trusted_publisher, class: "OIDC::PendingTrustedPublisher" do
+ sequence(:rubygem_name) { |n| "pending-rubygem#{n}" }
+ user
+ association :trusted_publisher, factory: :oidc_trusted_publisher_github_action
+ expires_at { 7.days.from_now }
+ end
+end
diff --git a/test/factories/oidc/rubygem_trusted_publishers.rb b/test/factories/oidc/rubygem_trusted_publishers.rb
new file mode 100644
index 00000000000..94c1f5cd751
--- /dev/null
+++ b/test/factories/oidc/rubygem_trusted_publishers.rb
@@ -0,0 +1,6 @@
+FactoryBot.define do
+ factory :oidc_rubygem_trusted_publisher, class: "OIDC::RubygemTrustedPublisher" do
+ rubygem
+ association :trusted_publisher, factory: :oidc_trusted_publisher_github_action
+ end
+end
diff --git a/test/factories/oidc/trusted_publisher/github_actions.rb b/test/factories/oidc/trusted_publisher/github_actions.rb
new file mode 100644
index 00000000000..4fa51fdd885
--- /dev/null
+++ b/test/factories/oidc/trusted_publisher/github_actions.rb
@@ -0,0 +1,9 @@
+FactoryBot.define do
+ factory :oidc_trusted_publisher_github_action, class: "OIDC::TrustedPublisher::GitHubAction" do
+ repository_owner { "example" }
+ sequence(:repository_name) { |n| "rubygem#{n}" }
+ repository_owner_id { "123456" }
+ workflow_filename { "push_gem.yml" }
+ environment { nil }
+ end
+end
diff --git a/test/integration/api/v1/oidc/trusted_publisher_controller_test.rb b/test/integration/api/v1/oidc/trusted_publisher_controller_test.rb
new file mode 100644
index 00000000000..69aa29a89da
--- /dev/null
+++ b/test/integration/api/v1/oidc/trusted_publisher_controller_test.rb
@@ -0,0 +1,208 @@
+require "test_helper"
+
+class Api::V1::OIDC::TrustedPublisherControllerTest < ActionDispatch::IntegrationTest
+ setup do
+ @pkey = OpenSSL::PKey::RSA.generate(2048)
+ create(:oidc_provider, issuer: OIDC::Provider::GITHUB_ACTIONS_ISSUER, pkey: @pkey)
+
+ @claims = {
+ "aud" => Gemcutter::HOST,
+ "exp" => 1_680_020_837,
+ "iat" => 1_680_020_537,
+ "iss" => "https://token.actions.githubusercontent.com",
+ "jti" => "79685b65-945d-450a-a3d8-a36bcf72c23d",
+ "nbf" => 1_680_019_937,
+ "ref" => "refs/heads/main",
+ "sha" => "04de3558bc5861874a86f8fcd67e516554101e71",
+ "sub" => "repo:segiddins/oidc-test:ref:refs/heads/main",
+ "actor" => "segiddins",
+ "run_id" => "4545231084",
+ "actor_id" => "1946610",
+ "base_ref" => "",
+ "head_ref" => "",
+ "ref_type" => "branch",
+ "workflow" => "token",
+ "event_name" => "push",
+ "repository" => "segiddins/oidc-test",
+ "run_number" => "4",
+ "run_attempt" => "1",
+ "workflow_ref" => "segiddins/oidc-test/.github/workflows/token.yml@refs/heads/main",
+ "workflow_sha" => "04de3558bc5861874a86f8fcd67e516554101e71",
+ "repository_id" => "620393838",
+ "job_workflow_ref" => "segiddins/oidc-test/.github/workflows/token.yml@refs/heads/main",
+ "job_workflow_sha" => "04de3558bc5861874a86f8fcd67e516554101e71",
+ "repository_owner" => "segiddins",
+ "runner_environment" => "github-hosted",
+ "repository_owner_id" => "1946610",
+ "repository_visibility" => "public"
+ }
+
+ travel_to Time.zone.at(1_680_020_830) # after the JWT iat, before the exp
+ end
+
+ def jwt(claims = @claims, key: @pkey)
+ JSON::JWT.new(claims).sign(key.to_jwk)
+ end
+
+ context "POST exchange_token" do
+ should "return not found with no matching trusted publisher" do
+ post api_v1_oidc_trusted_publisher_exchange_token_path,
+ params: { jwt: jwt.to_s }
+
+ assert_response :not_found
+ end
+
+ should "return not found when owner has changed" do
+ trusted_publisher = build(:oidc_trusted_publisher_github_action,
+ repository_name: "oidc-test",
+ repository_owner_id: "123",
+ workflow_filename: "token.yml")
+ trusted_publisher.repository_owner = "segiddins"
+ trusted_publisher.save!
+ post api_v1_oidc_trusted_publisher_exchange_token_path,
+ params: { jwt: jwt.to_s }
+
+ assert_response :not_found
+ end
+
+ should "return not found with an unknown issuer" do
+ @claims["iss"] = "https://unknown.example.com"
+ trusted_publisher = build(:oidc_trusted_publisher_github_action,
+ repository_name: "oidc-test",
+ repository_owner_id: "1946610",
+ workflow_filename: "token.yml")
+ trusted_publisher.repository_owner = "segiddins"
+ trusted_publisher.save!
+ post api_v1_oidc_trusted_publisher_exchange_token_path,
+ params: { jwt: jwt.to_s }
+
+ assert_response :not_found
+ end
+
+ should "return not found with an unsupported issuer" do
+ @claims["iss"] = "https://unknown.example.com"
+ create(:oidc_provider, issuer: @claims["iss"], pkey: @pkey)
+ trusted_publisher = build(:oidc_trusted_publisher_github_action,
+ repository_name: "oidc-test",
+ repository_owner_id: "1946610",
+ workflow_filename: "token.yml")
+ trusted_publisher.repository_owner = "segiddins"
+ trusted_publisher.save!
+ post api_v1_oidc_trusted_publisher_exchange_token_path,
+ params: { jwt: jwt.to_s }
+
+ assert_response :not_found
+ end
+
+ should "return bad request with an invalid JWT" do
+ post api_v1_oidc_trusted_publisher_exchange_token_path,
+ params: { jwt: "invalid" }
+
+ assert_response :bad_request
+ end
+
+ should "return bad request with invalid JSON" do
+ post api_v1_oidc_trusted_publisher_exchange_token_path,
+ params: { jwt: "a.a.a" }
+
+ assert_response :bad_request
+ end
+
+ should "return not found when time is before nbf" do
+ @claims["nbf"] += 1_000_000
+ trusted_publisher = build(:oidc_trusted_publisher_github_action,
+ repository_name: "oidc-test",
+ repository_owner_id: "1946610",
+ workflow_filename: "token.yml")
+ trusted_publisher.repository_owner = "segiddins"
+ trusted_publisher.save!
+ post api_v1_oidc_trusted_publisher_exchange_token_path,
+ params: { jwt: jwt.to_s }
+
+ assert_response :not_found
+ end
+
+ should "return not found when time is after exp" do
+ @claims["exp"] -= 1_000_000
+ trusted_publisher = build(:oidc_trusted_publisher_github_action,
+ repository_name: "oidc-test",
+ repository_owner_id: "1946610",
+ workflow_filename: "token.yml")
+ trusted_publisher.repository_owner = "segiddins"
+ trusted_publisher.save!
+ post api_v1_oidc_trusted_publisher_exchange_token_path,
+ params: { jwt: jwt.to_s }
+
+ assert_response :not_found
+ end
+
+ should "return not found when signature validation fails" do
+ @claims["exp"] -= 1_000_000
+ trusted_publisher = build(:oidc_trusted_publisher_github_action,
+ repository_name: "oidc-test",
+ repository_owner_id: "1946610",
+ workflow_filename: "token.yml")
+ trusted_publisher.repository_owner = "segiddins"
+ trusted_publisher.save!
+ post api_v1_oidc_trusted_publisher_exchange_token_path,
+ params: { jwt: jwt(key: OpenSSL::PKey::RSA.generate(2048)).to_s }
+
+ assert_response :not_found
+ end
+
+ should "return not found when workflow is from a different ref" do
+ @claims["job_workflow_ref"] = "segiddins/oidc-test/.github/workflows/token.yml@refs/heads/other"
+ trusted_publisher = build(:oidc_trusted_publisher_github_action,
+ repository_name: "oidc-test",
+ repository_owner_id: "1946610",
+ workflow_filename: "token.yml")
+ trusted_publisher.repository_owner = "segiddins"
+ trusted_publisher.save!
+ post api_v1_oidc_trusted_publisher_exchange_token_path,
+ params: { jwt: jwt.to_s }
+
+ assert_response :not_found
+ end
+
+ should "return not found when audience is wrong" do
+ @claims["aud"] = "other.com"
+ trusted_publisher = build(:oidc_trusted_publisher_github_action,
+ repository_name: "oidc-test",
+ repository_owner_id: "123",
+ workflow_filename: "token.yml")
+ trusted_publisher.repository_owner = "segiddins"
+ trusted_publisher.save!
+ post api_v1_oidc_trusted_publisher_exchange_token_path,
+ params: { jwt: jwt.to_s }
+
+ assert_response :not_found
+ end
+
+ should "succeed with matching trusted publisher" do
+ trusted_publisher = build(:oidc_trusted_publisher_github_action,
+ repository_name: "oidc-test",
+ repository_owner_id: "1946610",
+ workflow_filename: "token.yml")
+ trusted_publisher.repository_owner = "segiddins"
+ trusted_publisher.save!
+ post api_v1_oidc_trusted_publisher_exchange_token_path,
+ params: { jwt: jwt.to_s }
+
+ assert_response :success
+
+ resp = response.parsed_body
+
+ assert_match(/^rubygems_/, resp["rubygems_api_key"])
+ assert_equal({
+ "rubygems_api_key" => resp["rubygems_api_key"],
+ "name" => "GitHub Actions segiddins/oidc-test @ .github/workflows/token.yml 2023-03-28T16:22:17Z",
+ "scopes" => ["push_rubygem"],
+ "expires_at" => 15.minutes.from_now
+ }, resp)
+
+ api_key = trusted_publisher.api_keys.sole
+
+ assert_equal api_key.owner, trusted_publisher
+ end
+ end
+end
diff --git a/test/integration/api/v1/owner_test.rb b/test/integration/api/v1/owner_test.rb
index 550fbb613a4..ef95c7e8574 100644
--- a/test/integration/api/v1/owner_test.rb
+++ b/test/integration/api/v1/owner_test.rb
@@ -9,6 +9,10 @@ class Api::V1::OwnerTest < ActionDispatch::IntegrationTest
@other_user = create(:api_key, key: @other_user_api_key, add_owner: true, remove_owner: true).user
post session_path(session: { who: @user.handle, password: PasswordHelpers::SECURE_TEST_PASSWORD })
+ @trusted_publisher_api_key = "12325"
+ @trusted_publisher = create(:oidc_trusted_publisher_github_action)
+ create(:api_key, key: @trusted_publisher_api_key, owner: @trusted_publisher)
+
@rubygem = create(:rubygem, number: "1.0.0")
create(:ownership, user: @user, rubygem: @rubygem)
end
@@ -66,5 +70,17 @@ class Api::V1::OwnerTest < ActionDispatch::IntegrationTest
headers: { "HTTP_AUTHORIZATION" => @other_user_api_key }
assert_response :unauthorized
+
+ post api_v1_rubygem_owners_path(@rubygem.slug),
+ params: { email: @other_user.email },
+ headers: { "HTTP_AUTHORIZATION" => @trusted_publisher_api_key }
+
+ assert_response :forbidden
+
+ delete api_v1_rubygem_owners_path(@rubygem.slug),
+ params: { email: @other_user.email },
+ headers: { "HTTP_AUTHORIZATION" => @trusted_publisher_api_key }
+
+ assert_response :forbidden
end
end
diff --git a/test/integration/avo/oidc_pending_trusted_publishers_controller_test.rb b/test/integration/avo/oidc_pending_trusted_publishers_controller_test.rb
new file mode 100644
index 00000000000..46193b3da45
--- /dev/null
+++ b/test/integration/avo/oidc_pending_trusted_publishers_controller_test.rb
@@ -0,0 +1,27 @@
+require "test_helper"
+
+class Avo::OIDCPendingTrustedPublishersControllerTest < ActionDispatch::IntegrationTest
+ include AdminHelpers
+
+ test "getting pending trusted publishers as admin" do
+ admin_sign_in_as create(:admin_github_user, :is_admin)
+
+ get avo.resources_oidc_pending_trusted_publishers_path
+
+ assert_response :success
+
+ oidc_pending_trusted_publisher = create(:oidc_pending_trusted_publisher)
+
+ get avo.resources_oidc_pending_trusted_publishers_path
+
+ assert_response :success
+ page.assert_text oidc_pending_trusted_publisher.rubygem_name
+ page.assert_text oidc_pending_trusted_publisher.trusted_publisher.name
+
+ get avo.resources_oidc_pending_trusted_publisher_path(oidc_pending_trusted_publisher)
+
+ assert_response :success
+ page.assert_text oidc_pending_trusted_publisher.rubygem_name
+ page.assert_text oidc_pending_trusted_publisher.trusted_publisher.name
+ end
+end
diff --git a/test/integration/avo/oidc_rubygem_trusted_publishers_controller_test.rb b/test/integration/avo/oidc_rubygem_trusted_publishers_controller_test.rb
new file mode 100644
index 00000000000..7a9a643cd03
--- /dev/null
+++ b/test/integration/avo/oidc_rubygem_trusted_publishers_controller_test.rb
@@ -0,0 +1,27 @@
+require "test_helper"
+
+class Avo::OIDCRubygemTrustedPublishersControllerTest < ActionDispatch::IntegrationTest
+ include AdminHelpers
+
+ test "getting rubygem trusted publishers as admin" do
+ admin_sign_in_as create(:admin_github_user, :is_admin)
+
+ get avo.resources_oidc_rubygem_trusted_publishers_path
+
+ assert_response :success
+
+ oidc_rubygem_trusted_publisher = create(:oidc_rubygem_trusted_publisher)
+
+ get avo.resources_oidc_rubygem_trusted_publishers_path
+
+ assert_response :success
+ page.assert_text oidc_rubygem_trusted_publisher.rubygem.name
+ page.assert_text oidc_rubygem_trusted_publisher.trusted_publisher.name
+
+ get avo.resources_oidc_rubygem_trusted_publisher_path(oidc_rubygem_trusted_publisher)
+
+ assert_response :success
+ page.assert_text oidc_rubygem_trusted_publisher.rubygem.name
+ page.assert_text oidc_rubygem_trusted_publisher.trusted_publisher.name
+ end
+end
diff --git a/test/integration/avo/oidc_trusted_publisher_github_actions_controller_test.rb b/test/integration/avo/oidc_trusted_publisher_github_actions_controller_test.rb
new file mode 100644
index 00000000000..981aa0c9782
--- /dev/null
+++ b/test/integration/avo/oidc_trusted_publisher_github_actions_controller_test.rb
@@ -0,0 +1,25 @@
+require "test_helper"
+
+class Avo::OIDCTrustedPublisherGitHubActionsControllerTest < ActionDispatch::IntegrationTest
+ include AdminHelpers
+
+ test "getting github actions trusted publishers as admin" do
+ admin_sign_in_as create(:admin_github_user, :is_admin)
+
+ get avo.resources_oidc_trusted_publisher_github_actions_path
+
+ assert_response :success
+
+ oidc_trusted_publisher_github_action = create(:oidc_trusted_publisher_github_action)
+
+ get avo.resources_oidc_trusted_publisher_github_actions_path
+
+ assert_response :success
+ page.assert_text oidc_trusted_publisher_github_action.repository_owner
+
+ get avo.resources_oidc_trusted_publisher_github_action_path(oidc_trusted_publisher_github_action)
+
+ assert_response :success
+ page.assert_text oidc_trusted_publisher_github_action.repository_owner
+ end
+end
diff --git a/test/integration/oidc/pending_trusted_publishers_controller_test.rb b/test/integration/oidc/pending_trusted_publishers_controller_test.rb
new file mode 100644
index 00000000000..0f11ef9e26d
--- /dev/null
+++ b/test/integration/oidc/pending_trusted_publishers_controller_test.rb
@@ -0,0 +1,152 @@
+require "test_helper"
+
+class OIDC::PendingTrustedPublishersControllerTest < ActionDispatch::IntegrationTest
+ setup do
+ @user = create(:user, remember_token_expires_at: Gemcutter::REMEMBER_FOR.from_now)
+ post session_path(session: { who: @user.handle, password: PasswordHelpers::SECURE_TEST_PASSWORD })
+
+ @trusted_publisher = create(:oidc_pending_trusted_publisher, user: @user)
+ end
+
+ context "with a verified session" do
+ setup do
+ post(authenticate_session_path(verify_password: { password: PasswordHelpers::SECURE_TEST_PASSWORD }))
+ end
+
+ should "get index" do
+ get profile_oidc_pending_trusted_publishers_url
+
+ assert_response :success
+ end
+
+ should "get new" do
+ get new_profile_oidc_pending_trusted_publisher_url
+
+ assert_response :success
+ end
+
+ should "create trusted publisher" do
+ stub_request(:get, "https://api.github.com/users/example")
+ .to_return(status: 200, body: { id: "54321" }.to_json, headers: { "Content-Type" => "application/json" })
+
+ assert_difference("OIDC::PendingTrustedPublisher.count") do
+ trusted_publisher = build(:oidc_pending_trusted_publisher)
+ post profile_oidc_pending_trusted_publishers_url, params: {
+ oidc_pending_trusted_publisher: {
+ rubygem_name: trusted_publisher.rubygem_name,
+ trusted_publisher_type: trusted_publisher.trusted_publisher_type,
+ trusted_publisher_attributes: trusted_publisher.trusted_publisher.as_json
+ }
+ }
+ end
+
+ assert_redirected_to profile_oidc_pending_trusted_publishers_url
+ end
+
+ should "error creating trusted publisher with type" do
+ assert_no_difference("OIDC::PendingTrustedPublisher.count") do
+ post profile_oidc_pending_trusted_publishers_url, params: {
+ oidc_pending_trusted_publisher: {
+ rubygem_name: "rubygem1",
+ trusted_publisher_type: "Hash",
+ trusted_publisher_attributes: { repository_owner: "example" }
+ }
+ }
+
+ assert_response :redirect
+ assert_equal "Unsupported trusted publisher type", flash[:error]
+ end
+ end
+
+ should "error creating trusted publisher with unknown repository owner" do
+ stub_request(:get, "https://api.github.com/users/example")
+ .to_return(status: 404, body: { message: "Not Found" }.to_json, headers: { "Content-Type" => "application/json" })
+
+ assert_no_difference("OIDC::PendingTrustedPublisher.count") do
+ post profile_oidc_pending_trusted_publishers_url, params: {
+ oidc_pending_trusted_publisher: {
+ rubygem_name: "rubygem1",
+ trusted_publisher_type: OIDC::TrustedPublisher::GitHubAction.polymorphic_name,
+ trusted_publisher_attributes: { repository_owner: "example" }
+ }
+ }
+
+ assert_response :unprocessable_entity
+ assert_equal [
+ "Trusted publisher repository name can't be blank",
+ "Trusted publisher workflow filename can't be blank",
+ "Trusted publisher repository owner can't be blank"
+ ].to_sentence, flash[:error]
+ end
+ end
+
+ should "error creating invalid trusted publisher" do
+ stub_request(:get, "https://api.github.com/users/example")
+ .to_return(status: 200, body: { id: "54321" }.to_json, headers: { "Content-Type" => "application/json" })
+
+ assert_no_difference("OIDC::PendingTrustedPublisher.count") do
+ post profile_oidc_pending_trusted_publishers_url, params: {
+ oidc_pending_trusted_publisher: {
+ rubygem_name: "rubygem1",
+ trusted_publisher_type: OIDC::TrustedPublisher::GitHubAction.polymorphic_name,
+ trusted_publisher_attributes: { repository_name: "rubygem1", repository_owner: "example", workflow_filename: "ci.NO" }
+ }
+ }
+
+ assert_response :unprocessable_entity
+ assert_equal ["Trusted publisher workflow filename must end with .yml or .yaml"].to_sentence, flash[:error]
+ end
+ end
+
+ should "destroy trusted publisher" do
+ assert_difference("OIDC::PendingTrustedPublisher.count", -1) do
+ delete profile_oidc_pending_trusted_publisher_url(@trusted_publisher)
+ end
+
+ assert_redirected_to profile_oidc_pending_trusted_publishers_url
+
+ assert_raises ActiveRecord::RecordNotFound do
+ @trusted_publisher.reload
+ end
+ end
+
+ should "return not found on destroy for other users trusted publisher" do
+ trusted_publisher = create(:oidc_pending_trusted_publisher)
+ assert_no_difference("OIDC::PendingTrustedPublisher.count") do
+ delete profile_oidc_pending_trusted_publisher_url(trusted_publisher)
+
+ assert_response :not_found
+ end
+ end
+ end
+
+ context "without a verified session" do
+ should "redirect index to verify" do
+ get profile_oidc_pending_trusted_publishers_url
+
+ assert_response :redirect
+ assert_redirected_to verify_session_path
+ end
+
+ should "redirect new to verify" do
+ get new_profile_oidc_pending_trusted_publisher_url
+
+ assert_response :redirect
+ assert_redirected_to verify_session_path
+ end
+
+ should "redirect create to verify" do
+ post profile_oidc_pending_trusted_publishers_url
+
+ assert_response :redirect
+ assert_redirected_to verify_session_path
+ end
+
+ should "redirect destroy to verify" do
+ delete new_profile_oidc_pending_trusted_publisher_url
+
+ assert_response :redirect
+ assert_redirected_to verify_session_path
+ end
+ end
+end
diff --git a/test/integration/oidc/rubygem_trusted_publishers_controller_test.rb b/test/integration/oidc/rubygem_trusted_publishers_controller_test.rb
new file mode 100644
index 00000000000..d695739859e
--- /dev/null
+++ b/test/integration/oidc/rubygem_trusted_publishers_controller_test.rb
@@ -0,0 +1,185 @@
+require "test_helper"
+
+class OIDC::RubygemTrustedPublishersControllerTest < ActionDispatch::IntegrationTest
+ setup do
+ @user = create(:user, remember_token_expires_at: Gemcutter::REMEMBER_FOR.from_now)
+ post session_path(session: { who: @user.handle, password: PasswordHelpers::SECURE_TEST_PASSWORD })
+
+ @rubygem = create(:rubygem, owners: [@user])
+ create(:version, rubygem: @rubygem)
+ @trusted_publisher = create(:oidc_rubygem_trusted_publisher, rubygem: @rubygem)
+ end
+
+ context "with a verified session" do
+ setup do
+ post(authenticate_session_path(verify_password: { password: PasswordHelpers::SECURE_TEST_PASSWORD }))
+ end
+
+ should "respond forbidden for non-owner" do
+ @rubygem.disown
+
+ get rubygem_trusted_publishers_url(@rubygem.slug)
+
+ assert_response :forbidden
+ end
+
+ should "get index" do
+ create(:oidc_rubygem_trusted_publisher, rubygem: @rubygem,
+ trusted_publisher: create(:oidc_trusted_publisher_github_action, environment: "production"))
+ get rubygem_trusted_publishers_url(@rubygem.slug)
+
+ assert_response :success
+ end
+
+ should "get new" do
+ get new_rubygem_trusted_publisher_url(@rubygem.slug)
+
+ assert_response :success
+ end
+
+ should "get new for a github rubygem" do
+ stub_request(:get, "https://api.github.com/repos/example/rubygem1/contents/.github/workflows")
+ .to_return(status: 200, body: [
+ { name: "ci.yml", type: "file" },
+ { name: "push_rubygem.yml", type: "file" },
+ { name: "push_README.md", type: "file" },
+ { name: "push.yml", type: "directory" }
+ ].to_json, headers: { "Content-Type" => "application/json" })
+
+ create(:version, rubygem: @rubygem, metadata: { "source_code_uri" => "https://github.com/example/rubygem1" })
+
+ get new_rubygem_trusted_publisher_url(@rubygem.slug)
+
+ assert_response :success
+
+ page.assert_selector("input[name='oidc_rubygem_trusted_publisher[trusted_publisher_attributes][repository_owner]'][value='example']")
+ page.assert_selector("input[name='oidc_rubygem_trusted_publisher[trusted_publisher_attributes][repository_name]'][value='rubygem1']")
+ page.assert_selector("input[name='oidc_rubygem_trusted_publisher[trusted_publisher_attributes][workflow_filename]'][value='push_rubygem.yml']")
+ end
+
+ should "get new for a github rubygem with no found workflows" do
+ stub_request(:get, "https://api.github.com/repos/example/rubygem1/contents/.github/workflows")
+ .to_return(status: 404, body: { message: "Not Found" }.to_json, headers: { "Content-Type" => "application/json" })
+
+ create(:version, rubygem: @rubygem, metadata: { "source_code_uri" => "https://github.com/example/rubygem1" })
+
+ get new_rubygem_trusted_publisher_url(@rubygem.slug)
+
+ assert_response :success
+
+ page.assert_selector("input[name='oidc_rubygem_trusted_publisher[trusted_publisher_attributes][repository_owner]'][value='example']")
+ page.assert_selector("input[name='oidc_rubygem_trusted_publisher[trusted_publisher_attributes][repository_name]'][value='rubygem1']")
+ end
+
+ should "create trusted publisher" do
+ stub_request(:get, "https://api.github.com/users/example")
+ .to_return(status: 200, body: { id: "54321" }.to_json, headers: { "Content-Type" => "application/json" })
+
+ assert_difference("OIDC::RubygemTrustedPublisher.count") do
+ trusted_publisher = build(:oidc_rubygem_trusted_publisher, rubygem: @rubygem)
+ post rubygem_trusted_publishers_url(@rubygem.slug), params: {
+ oidc_rubygem_trusted_publisher: {
+ trusted_publisher_type: trusted_publisher.trusted_publisher_type,
+ trusted_publisher_attributes: trusted_publisher.trusted_publisher.as_json
+ }
+ }
+ end
+
+ assert_redirected_to rubygem_trusted_publishers_url(@rubygem.slug)
+ end
+
+ should "error creating trusted publisher with type" do
+ assert_no_difference("OIDC::RubygemTrustedPublisher.count") do
+ post rubygem_trusted_publishers_url(@rubygem.slug), params: {
+ oidc_rubygem_trusted_publisher: {
+ trusted_publisher_type: "Hash",
+ trusted_publisher_attributes: { repository_owner: "example" }
+ }
+ }
+
+ assert_response :redirect
+ assert_equal "Unsupported trusted publisher type", flash[:error]
+ end
+ end
+
+ should "error creating trusted publisher with unknown repository owner" do
+ stub_request(:get, "https://api.github.com/users/example")
+ .to_return(status: 404, body: { message: "Not Found" }.to_json, headers: { "Content-Type" => "application/json" })
+
+ assert_no_difference("OIDC::RubygemTrustedPublisher.count") do
+ post rubygem_trusted_publishers_url(@rubygem.slug), params: {
+ oidc_rubygem_trusted_publisher: {
+ trusted_publisher_type: OIDC::TrustedPublisher::GitHubAction.polymorphic_name,
+ trusted_publisher_attributes: { repository_owner: "example" }
+ }
+ }
+
+ assert_response :unprocessable_entity
+ assert_equal [
+ "Trusted publisher repository name can't be blank",
+ "Trusted publisher workflow filename can't be blank",
+ "Trusted publisher repository owner can't be blank"
+ ].to_sentence, flash[:error]
+ end
+ end
+
+ should "error creating invalid trusted publisher" do
+ stub_request(:get, "https://api.github.com/users/example")
+ .to_return(status: 200, body: { id: "54321" }.to_json, headers: { "Content-Type" => "application/json" })
+
+ assert_no_difference("OIDC::RubygemTrustedPublisher.count") do
+ post rubygem_trusted_publishers_url(@rubygem.slug), params: {
+ oidc_rubygem_trusted_publisher: {
+ trusted_publisher_type: OIDC::TrustedPublisher::GitHubAction.polymorphic_name,
+ trusted_publisher_attributes: { repository_name: "rubygem1", repository_owner: "example", workflow_filename: "ci.NO" }
+ }
+ }
+
+ assert_response :unprocessable_entity
+ assert_equal ["Trusted publisher workflow filename must end with .yml or .yaml"].to_sentence, flash[:error]
+ end
+ end
+
+ should "destroy trusted publisher" do
+ assert_difference("OIDC::RubygemTrustedPublisher.count", -1) do
+ delete rubygem_trusted_publisher_url(@rubygem.slug, @trusted_publisher)
+ end
+
+ assert_redirected_to rubygem_trusted_publishers_url(@rubygem.slug)
+
+ assert_raises ActiveRecord::RecordNotFound do
+ @trusted_publisher.reload
+ end
+ end
+ end
+
+ context "without a verified session" do
+ should "redirect index to verify" do
+ get rubygem_trusted_publishers_url(@rubygem.slug)
+
+ assert_response :redirect
+ assert_redirected_to verify_session_path
+ end
+
+ should "redirect new to verify" do
+ get new_rubygem_trusted_publisher_url(@rubygem.slug)
+
+ assert_response :redirect
+ assert_redirected_to verify_session_path
+ end
+
+ should "redirect create to verify" do
+ post rubygem_trusted_publishers_url(@rubygem.slug)
+
+ assert_response :redirect
+ assert_redirected_to verify_session_path
+ end
+
+ should "redirect destroy to verify" do
+ delete new_rubygem_trusted_publisher_url(@rubygem.slug)
+
+ assert_response :redirect
+ assert_redirected_to verify_session_path
+ end
+ end
+end
diff --git a/test/integration/push_test.rb b/test/integration/push_test.rb
index 28115c8f150..af733526118 100644
--- a/test/integration/push_test.rb
+++ b/test/integration/push_test.rb
@@ -59,6 +59,76 @@ class PushTest < ActionDispatch::IntegrationTest
assert page.has_content?("2.0.0")
end
+ test "pushing a new version of a gem with a trusted publisher" do
+ rubygem = create(:rubygem, name: "sandworm", number: "1.0.0")
+ create(:ownership, rubygem: rubygem, user: @user)
+
+ rubygem_trusted_publisher = create(:oidc_rubygem_trusted_publisher, rubygem: rubygem)
+
+ @key = "543321"
+ create(:api_key, owner: rubygem_trusted_publisher.trusted_publisher, key: @key, push_rubygem: true)
+
+ build_gem "sandworm", "2.0.0"
+
+ push_gem "sandworm-2.0.0.gem"
+
+ assert_response :success
+
+ get rubygem_path("sandworm")
+
+ assert_response :success
+ page.assert_text("Pushed by")
+ page.assert_selector(:xpath, ".//img[@title=#{rubygem_trusted_publisher.trusted_publisher.name.inspect}]")
+ end
+
+ test "pushing a new gem with a pending trusted publisher" do
+ pending_trusted_publisher = create(:oidc_pending_trusted_publisher, rubygem_name: "sandworm", user: @user)
+
+ @key = "543321"
+ create(:api_key, owner: pending_trusted_publisher.trusted_publisher, key: @key, push_rubygem: true)
+
+ build_gem "sandworm", "2.0.0"
+
+ push_gem "sandworm-2.0.0.gem"
+
+ assert_response :success
+
+ get rubygem_path("sandworm")
+
+ assert_response :success
+ page.assert_text("Pushed by")
+ page.assert_selector(:xpath, ".//img[@title=#{pending_trusted_publisher.trusted_publisher.name.inspect}]")
+
+ rubygem = Rubygem.find_by!(name: "sandworm")
+
+ assert rubygem.owned_by?(@user)
+ assert rubygem.oidc_rubygem_trusted_publishers.exists?(trusted_publisher: pending_trusted_publisher.trusted_publisher)
+ end
+
+ test "pushing a new gem with a pending trusted publisher case insensitive" do
+ pending_trusted_publisher = create(:oidc_pending_trusted_publisher, rubygem_name: "SaNdWoRm", user: @user)
+
+ @key = "543321"
+ create(:api_key, owner: pending_trusted_publisher.trusted_publisher, key: @key, push_rubygem: true)
+
+ build_gem "sandworm", "2.0.0"
+
+ push_gem "sandworm-2.0.0.gem"
+
+ assert_response :success
+
+ get rubygem_path("sandworm")
+
+ assert_response :success
+ page.assert_text("Pushed by")
+ page.assert_selector(:xpath, ".//img[@title=#{pending_trusted_publisher.trusted_publisher.name.inspect}]")
+
+ rubygem = Rubygem.find_by!(name: "sandworm")
+
+ assert rubygem.owned_by?(@user)
+ assert rubygem.oidc_rubygem_trusted_publishers.exists?(trusted_publisher: pending_trusted_publisher.trusted_publisher)
+ end
+
test "pushing a gem with a known dependency" do
rubygem = create(:rubygem, name: "crysknife", number: "1.0.0")
diff --git a/test/mailers/previews/mailer_preview.rb b/test/mailers/previews/mailer_preview.rb
index ad0dff8d879..ff7e7bf7945 100644
--- a/test/mailers/previews/mailer_preview.rb
+++ b/test/mailers/previews/mailer_preview.rb
@@ -33,6 +33,19 @@ def gem_pushed
Mailer.gem_pushed(ownership.user, ownership.rubygem.versions.last.id, ownership.user_id)
end
+ def gem_pushed_by_trusted_publisher
+ ownership = Ownership.where.not(user: nil).where(push_notifier: true).last
+
+ Mailer.gem_pushed(OIDC::RubygemTrustedPublisher.first.trusted_publisher, ownership.rubygem.versions.last.id, ownership.user_id)
+ end
+
+ def gem_trusted_publisher_added
+ rubygem_trusted_publisher = OIDC::RubygemTrustedPublisher.last
+ created_by_user = User.last
+ notified_user = User.first
+ Mailer.gem_trusted_publisher_added(rubygem_trusted_publisher, created_by_user, notified_user)
+ end
+
def mfa_notification
Mailer.mfa_notification(User.last.id)
end
@@ -89,7 +102,7 @@ def api_key_created
end
def api_key_created_oidc_api_key_role
- api_key = OIDC::IdToken.last.api_key
+ api_key = OIDC::IdToken.where.not(api_key_role: nil).last.api_key
Mailer.api_key_created(api_key.id)
end
diff --git a/test/models/oidc/pending_trusted_publisher_test.rb b/test/models/oidc/pending_trusted_publisher_test.rb
new file mode 100644
index 00000000000..c1c6a79d603
--- /dev/null
+++ b/test/models/oidc/pending_trusted_publisher_test.rb
@@ -0,0 +1,29 @@
+require "test_helper"
+
+class OIDC::PendingTrustedPublisherTest < ActiveSupport::TestCase
+ setup do
+ @pending_trusted_publisher = build(:oidc_pending_trusted_publisher)
+ end
+ subject { @pending_trusted_publisher }
+
+ should belong_to(:trusted_publisher)
+ should belong_to(:user)
+
+ should validate_presence_of(:rubygem_name)
+ should validate_uniqueness_of(:rubygem_name).scoped_to(:trusted_publisher_id, :trusted_publisher_type).case_insensitive
+
+ test "validates rubygem name is available" do
+ publisher = build(:oidc_pending_trusted_publisher, rubygem_name: "foo")
+
+ assert_predicate publisher, :valid?
+
+ rubygem = create(:rubygem, name: "foo")
+
+ assert_predicate publisher, :valid?
+
+ create(:version, rubygem: rubygem)
+
+ refute_predicate publisher, :valid?
+ assert_equal ["is already in use"], publisher.errors[:rubygem_name]
+ end
+end
diff --git a/test/models/oidc/rubygem_trusted_publisher_test.rb b/test/models/oidc/rubygem_trusted_publisher_test.rb
new file mode 100644
index 00000000000..57e3f51c209
--- /dev/null
+++ b/test/models/oidc/rubygem_trusted_publisher_test.rb
@@ -0,0 +1,13 @@
+require "test_helper"
+
+class OIDC::RubygemTrustedPublisherTest < ActiveSupport::TestCase
+ setup do
+ @rubygem_trusted_publisher = build(:oidc_rubygem_trusted_publisher)
+ end
+ subject { @rubygem_trusted_publisher }
+
+ should belong_to(:rubygem)
+ should belong_to(:trusted_publisher)
+
+ should validate_uniqueness_of(:rubygem).scoped_to(:trusted_publisher_id, :trusted_publisher_type)
+end
diff --git a/test/models/oidc/trusted_publisher/github_action_test.rb b/test/models/oidc/trusted_publisher/github_action_test.rb
new file mode 100644
index 00000000000..b87e8e63e8f
--- /dev/null
+++ b/test/models/oidc/trusted_publisher/github_action_test.rb
@@ -0,0 +1,138 @@
+require "test_helper"
+
+class OIDC::TrustedPublisher::GitHubActionTest < ActiveSupport::TestCase
+ make_my_diffs_pretty!
+
+ should have_many(:rubygems)
+ should have_many(:rubygem_trusted_publishers)
+ should have_many(:api_keys).inverse_of(:owner)
+
+ should validate_presence_of(:repository_owner)
+ should validate_presence_of(:repository_name)
+ should validate_presence_of(:workflow_filename)
+ should validate_presence_of(:repository_owner_id)
+
+ test "validates publisher uniqueness" do
+ publisher = create(:oidc_trusted_publisher_github_action)
+ assert_raises(ActiveRecord::RecordInvalid) do
+ create(:oidc_trusted_publisher_github_action, repository_owner: publisher.repository_owner,
+ repository_name: publisher.repository_name, workflow_filename: publisher.workflow_filename,
+ repository_owner_id: publisher.repository_owner_id, environment: publisher.environment)
+ end
+ end
+
+ test ".for_claims" do
+ bar_other_owner_id = create(:oidc_trusted_publisher_github_action, repository_name: "bar")
+ bar_other_owner_id.update!(repository_owner_id: "654321")
+ bar = create(:oidc_trusted_publisher_github_action, repository_name: "bar")
+ bar_test = create(:oidc_trusted_publisher_github_action, repository_name: "bar", environment: "test")
+ _bar_dev = create(:oidc_trusted_publisher_github_action, repository_name: "bar", environment: "dev")
+ create(:oidc_trusted_publisher_github_action, repository_name: "foo")
+
+ claims = {
+ repository: "example/bar",
+ job_workflow_ref: "example/bar/.github/workflows/push_gem.yml@refs/heads/main",
+ ref: "refs/heads/main",
+ sha: "04de3558bc5861874a86f8fcd67e516554101e71",
+ repository_owner_id: "123456"
+ }
+
+ assert_equal bar, OIDC::TrustedPublisher::GitHubAction.for_claims(claims)
+ assert_equal bar, OIDC::TrustedPublisher::GitHubAction.for_claims(claims.merge(environment: nil))
+ assert_equal bar, OIDC::TrustedPublisher::GitHubAction.for_claims(claims.merge(environment: "other"))
+ assert_equal bar_test, OIDC::TrustedPublisher::GitHubAction.for_claims(claims.merge(environment: "test"))
+ end
+
+ test "#name" do
+ publisher = create(:oidc_trusted_publisher_github_action, repository_name: "bar")
+
+ assert_equal "GitHub Actions example/bar @ .github/workflows/push_gem.yml", publisher.name
+
+ publisher.update!(environment: "test")
+
+ assert_equal "GitHub Actions example/bar @ .github/workflows/push_gem.yml (test)", publisher.name
+ end
+
+ test "#owns_gem?" do
+ rubygem1 = create(:rubygem)
+ rubygem2 = create(:rubygem)
+
+ publisher = create(:oidc_trusted_publisher_github_action)
+ create(:oidc_rubygem_trusted_publisher, trusted_publisher: publisher, rubygem: rubygem1)
+
+ assert publisher.owns_gem?(rubygem1)
+ refute publisher.owns_gem?(rubygem2)
+ end
+
+ test "#to_access_policy" do
+ publisher = create(:oidc_trusted_publisher_github_action, repository_name: "rubygem1")
+
+ assert_equal(
+ {
+ statements: [
+ {
+ effect: "allow",
+ principal: {
+ oidc: "https://token.actions.githubusercontent.com"
+ },
+ conditions: [
+ { operator: "string_equals", claim: "repository", value: "example/rubygem1" },
+ { operator: "string_equals", claim: "repository_owner_id", value: "123456" },
+ { operator: "string_equals", claim: "aud", value: Gemcutter::HOST },
+ { operator: "string_equals", claim: "job_workflow_ref", value: "example/rubygem1/.github/workflows/push_gem.yml@ref" }
+ ]
+ },
+ {
+ effect: "allow",
+ principal: {
+ oidc: "https://token.actions.githubusercontent.com"
+ },
+ conditions: [
+ { operator: "string_equals", claim: "repository", value: "example/rubygem1" },
+ { operator: "string_equals", claim: "repository_owner_id", value: "123456" },
+ { operator: "string_equals", claim: "aud", value: Gemcutter::HOST },
+ { operator: "string_equals", claim: "job_workflow_ref", value: "example/rubygem1/.github/workflows/push_gem.yml@sha" }
+ ]
+ }
+ ]
+ }.deep_stringify_keys,
+ publisher.to_access_policy({ ref: "ref", sha: "sha" }).as_json
+ )
+
+ publisher.update!(environment: "test")
+
+ assert_equal(
+ {
+ statements: [
+ {
+ effect: "allow",
+ principal: {
+ oidc: "https://token.actions.githubusercontent.com"
+ },
+ conditions: [
+ { operator: "string_equals", claim: "repository", value: "example/rubygem1" },
+ { operator: "string_equals", claim: "environment", value: "test" },
+ { operator: "string_equals", claim: "repository_owner_id", value: "123456" },
+ { operator: "string_equals", claim: "aud", value: Gemcutter::HOST },
+ { operator: "string_equals", claim: "job_workflow_ref", value: "example/rubygem1/.github/workflows/push_gem.yml@ref" }
+ ]
+ },
+ {
+ effect: "allow",
+ principal: {
+ oidc: "https://token.actions.githubusercontent.com"
+ },
+ conditions: [
+ { operator: "string_equals", claim: "repository", value: "example/rubygem1" },
+ { operator: "string_equals", claim: "environment", value: "test" },
+ { operator: "string_equals", claim: "repository_owner_id", value: "123456" },
+ { operator: "string_equals", claim: "aud", value: Gemcutter::HOST },
+ { operator: "string_equals", claim: "job_workflow_ref", value: "example/rubygem1/.github/workflows/push_gem.yml@sha" }
+ ]
+ }
+ ]
+ }.deep_stringify_keys,
+ publisher.to_access_policy({ ref: "ref", sha: "sha" }).as_json
+ )
+ end
+end
diff --git a/test/policies/oidc/pending_trusted_publisher_policy_test.rb b/test/policies/oidc/pending_trusted_publisher_policy_test.rb
new file mode 100644
index 00000000000..74c921e3bdd
--- /dev/null
+++ b/test/policies/oidc/pending_trusted_publisher_policy_test.rb
@@ -0,0 +1,42 @@
+require "test_helper"
+
+class OIDC::PendingTrustedPublisherPolicyTest < ActiveSupport::TestCase
+ setup do
+ @pending_trusted_publisher = create(:oidc_pending_trusted_publisher)
+
+ @admin = create(:admin_github_user, :is_admin)
+ @non_admin = create(:admin_github_user)
+ end
+
+ def test_scope
+ assert_equal [@pending_trusted_publisher], Pundit.policy_scope!(
+ @admin,
+ OIDC::PendingTrustedPublisher
+ ).to_a
+ end
+
+ def test_avo_index
+ assert_predicate Pundit.policy!(@admin, OIDC::PendingTrustedPublisher), :avo_index?
+ refute_predicate Pundit.policy!(@non_admin, OIDC::PendingTrustedPublisher), :avo_index?
+ end
+
+ def test_avo_show
+ assert_predicate Pundit.policy!(@admin, @pending_trusted_publisher), :avo_show?
+ refute_predicate Pundit.policy!(@non_admin, @pending_trusted_publisher), :avo_show?
+ end
+
+ def test_avo_create
+ refute_predicate Pundit.policy!(@admin, OIDC::PendingTrustedPublisher), :avo_create?
+ refute_predicate Pundit.policy!(@non_admin, OIDC::PendingTrustedPublisher), :avo_create?
+ end
+
+ def test_avo_update
+ refute_predicate Pundit.policy!(@admin, @pending_trusted_publisher), :avo_update?
+ refute_predicate Pundit.policy!(@non_admin, @pending_trusted_publisher), :avo_update?
+ end
+
+ def test_avo_destroy
+ refute_predicate Pundit.policy!(@admin, @pending_trusted_publisher), :avo_destroy?
+ refute_predicate Pundit.policy!(@non_admin, @pending_trusted_publisher), :avo_destroy?
+ end
+end
diff --git a/test/policies/oidc/rubygem_trusted_publisher_policy_test.rb b/test/policies/oidc/rubygem_trusted_publisher_policy_test.rb
new file mode 100644
index 00000000000..1ec4e6c33cc
--- /dev/null
+++ b/test/policies/oidc/rubygem_trusted_publisher_policy_test.rb
@@ -0,0 +1,42 @@
+require "test_helper"
+
+class OIDC::RubygemTrustedPublisherPolicyTest < ActiveSupport::TestCase
+ setup do
+ @rubygem_trusted_publisher = create(:oidc_rubygem_trusted_publisher)
+
+ @admin = create(:admin_github_user, :is_admin)
+ @non_admin = create(:admin_github_user)
+ end
+
+ def test_scope
+ assert_equal [@rubygem_trusted_publisher], Pundit.policy_scope!(
+ @admin,
+ OIDC::RubygemTrustedPublisher
+ ).to_a
+ end
+
+ def test_avo_index
+ assert_predicate Pundit.policy!(@admin, OIDC::RubygemTrustedPublisher), :avo_index?
+ refute_predicate Pundit.policy!(@non_admin, OIDC::RubygemTrustedPublisher), :avo_index?
+ end
+
+ def test_avo_show
+ assert_predicate Pundit.policy!(@admin, @rubygem_trusted_publisher), :avo_show?
+ refute_predicate Pundit.policy!(@non_admin, @rubygem_trusted_publisher), :avo_show?
+ end
+
+ def test_avo_create
+ refute_predicate Pundit.policy!(@admin, OIDC::RubygemTrustedPublisher), :avo_create?
+ refute_predicate Pundit.policy!(@non_admin, OIDC::RubygemTrustedPublisher), :avo_create?
+ end
+
+ def test_avo_update
+ refute_predicate Pundit.policy!(@admin, @rubygem_trusted_publisher), :avo_update?
+ refute_predicate Pundit.policy!(@non_admin, @rubygem_trusted_publisher), :avo_update?
+ end
+
+ def test_avo_destroy
+ refute_predicate Pundit.policy!(@admin, @rubygem_trusted_publisher), :avo_destroy?
+ refute_predicate Pundit.policy!(@non_admin, @rubygem_trusted_publisher), :avo_destroy?
+ end
+end
diff --git a/test/policies/oidc/trusted_publisher/github_action_policy_test.rb b/test/policies/oidc/trusted_publisher/github_action_policy_test.rb
new file mode 100644
index 00000000000..4d00f78ab02
--- /dev/null
+++ b/test/policies/oidc/trusted_publisher/github_action_policy_test.rb
@@ -0,0 +1,42 @@
+require "test_helper"
+
+class OIDC::TrustedPublisher::GitHubActionPolicyTest < ActiveSupport::TestCase
+ setup do
+ @trusted_publisher_github_action = create(:oidc_trusted_publisher_github_action)
+
+ @admin = create(:admin_github_user, :is_admin)
+ @non_admin = create(:admin_github_user)
+ end
+
+ def test_scope
+ assert_equal [@trusted_publisher_github_action], Pundit.policy_scope!(
+ @admin,
+ OIDC::TrustedPublisher::GitHubAction
+ ).to_a
+ end
+
+ def test_avo_index
+ assert_predicate Pundit.policy!(@admin, OIDC::TrustedPublisher::GitHubAction), :avo_index?
+ refute_predicate Pundit.policy!(@non_admin, OIDC::TrustedPublisher::GitHubAction), :avo_index?
+ end
+
+ def test_avo_show
+ assert_predicate Pundit.policy!(@admin, @trusted_publisher_github_action), :avo_show?
+ refute_predicate Pundit.policy!(@non_admin, @trusted_publisher_github_action), :avo_show?
+ end
+
+ def test_avo_create
+ refute_predicate Pundit.policy!(@admin, OIDC::TrustedPublisher::GitHubAction), :avo_create?
+ refute_predicate Pundit.policy!(@non_admin, OIDC::TrustedPublisher::GitHubAction), :avo_create?
+ end
+
+ def test_avo_update
+ refute_predicate Pundit.policy!(@admin, @trusted_publisher_github_action), :avo_update?
+ refute_predicate Pundit.policy!(@non_admin, @trusted_publisher_github_action), :avo_update?
+ end
+
+ def test_avo_destroy
+ refute_predicate Pundit.policy!(@admin, @trusted_publisher_github_action), :avo_destroy?
+ refute_predicate Pundit.policy!(@non_admin, @trusted_publisher_github_action), :avo_destroy?
+ end
+end
diff --git a/test/system/oidc_test.rb b/test/system/oidc_test.rb
index 7c4b17cbff4..2f42f6eb0d5 100644
--- a/test/system/oidc_test.rb
+++ b/test/system/oidc_test.rb
@@ -214,4 +214,142 @@ def verify_session # rubocop:disable Minitest/TestMethodName
}
), role.reload.as_json.slice(*expected.keys))
end
+
+ test "creating rubygem trusted publishers" do
+ rubygem = create(:rubygem, name: "rubygem0")
+ create(:version, rubygem: rubygem, metadata: { "source_code_uri" => "https://github.com/example/rubygem0" })
+
+ visit new_rubygem_trusted_publisher_path(rubygem.slug)
+
+ assert_text "Please sign in to continue."
+
+ sign_in
+ visit new_rubygem_trusted_publisher_path(rubygem.slug)
+ verify_session
+
+ assert_text "forbidden"
+
+ create(:ownership, rubygem: rubygem, user: @user)
+
+ visit rubygem_trusted_publishers_path(rubygem.slug)
+
+ page.assert_selector "h1", text: "Trusted Publishers"
+ page.assert_text("Trusted publishers for rubygem0")
+ page.assert_text "NO RUBYGEM TRUSTED PUBLISHERS FOUND"
+
+ stub_request(:get, "https://api.github.com/repos/example/rubygem0/contents/.github/workflows")
+ .to_return(status: 200, body: [
+ { name: "ci.yml", type: "file" },
+ { name: "push_rubygem.yml", type: "file" },
+ { name: "push_README.md", type: "file" },
+ { name: "push.yml", type: "directory" }
+ ].to_json, headers: { "Content-Type" => "application/json" })
+
+ click_button "Create"
+
+ page.assert_selector "h1", text: "New Trusted Publisher"
+
+ assert_field "Repository owner", with: "example"
+ assert_field "Repository name", with: "rubygem0"
+ assert_field "Workflow filename", with: "push_rubygem.yml"
+ assert_field "Environment", with: ""
+
+ stub_request(:get, "https://api.github.com/users/example")
+ .to_return(status: 200, body: { id: "54321" }.to_json, headers: { "Content-Type" => "application/json" })
+
+ click_button "Create Rubygem trusted publisher"
+
+ page.assert_text "Trusted Publisher created"
+ page.assert_selector "h1", text: "Trusted Publishers"
+ page.assert_text("Trusted publishers for rubygem0")
+ page.assert_text "GitHub Actions\nDelete\nGitHub Repository\nexample/rubygem0\nWorkflow Filename\npush_rubygem.yml"
+ end
+
+ test "deleting rubygem trusted publishers" do
+ rubygem = create(:rubygem, owners: [@user])
+ create(:oidc_rubygem_trusted_publisher, rubygem:)
+ create(:version, rubygem:)
+
+ sign_in
+ visit rubygem_trusted_publishers_path(rubygem.slug)
+ verify_session
+
+ click_button "Delete"
+
+ page.assert_text "Trusted Publisher deleted"
+ page.assert_text "NO RUBYGEM TRUSTED PUBLISHERS FOUND"
+ end
+
+ test "creating pending trusted publishers" do
+ rubygem = create(:rubygem, name: "rubygem0")
+ create(:version, rubygem: rubygem, metadata: { "source_code_uri" => "https://github.com/example/rubygem0" })
+
+ visit profile_oidc_pending_trusted_publishers_path
+
+ assert_text "Please sign in to continue."
+
+ sign_in
+ visit profile_oidc_pending_trusted_publishers_path
+ verify_session
+ click_button "Create"
+
+ page.assert_selector "h1", text: "New Pending Trusted Publisher"
+
+ click_button "Create"
+
+ page.assert_text "can't be blank"
+ page.assert_selector "h1", text: "New Pending Trusted Publisher"
+
+ page.fill_in "RubyGem name", with: "rubygem0"
+ page.fill_in "Repository owner", with: "example"
+ page.fill_in "Repository name", with: "rubygem1"
+ page.fill_in "Workflow filename", with: "push_rubygem.yml"
+ page.fill_in "Environment", with: "prod"
+
+ stub_request(:get, "https://api.github.com/users/example")
+ .to_return(status: 200, body: { id: "54321" }.to_json, headers: { "Content-Type" => "application/json" })
+
+ click_button "Create"
+
+ page.assert_text "RubyGem name is already in use"
+ page.assert_selector "h1", text: "New Pending Trusted Publisher"
+
+ assert_field "RubyGem name", with: "rubygem0"
+ assert_field "Repository owner", with: "example"
+ assert_field "Repository name", with: "rubygem1"
+ assert_field "Workflow filename", with: "push_rubygem.yml"
+ assert_field "Environment", with: "prod"
+
+ page.fill_in "RubyGem name", with: "rubygem1"
+
+ click_button "Create Pending trusted publisher"
+
+ page.assert_text "Pending Trusted Publisher created"
+ page.assert_selector "h1", text: "Pending Trusted Publishers"
+ page.assert_text <<~TEXT
+ rubygem1
+ Delete
+ GitHub Actions
+ Valid for about 12 hours
+ GitHub Repository
+ example/rubygem1
+ Workflow Filename
+ push_rubygem.yml
+ Environment
+ prod
+ TEXT
+ end
+
+ test "deleting pending trusted publishers" do
+ create(:oidc_pending_trusted_publisher, user: @user)
+
+ sign_in
+ visit profile_oidc_pending_trusted_publishers_path
+ verify_session
+
+ click_button "Delete"
+
+ page.assert_text "Pending Trusted Publisher deleted"
+ page.assert_text "NO PENDING TRUSTED PUBLISHERS FOUND"
+ end
end
From 92757a7ed486b29995e095ce61a72df7544fcdd2 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 11 Dec 2023 11:21:24 -0800
Subject: [PATCH 062/112] Bump tailwindcss-rails from 2.0.32 to 2.0.33 (#4280)
Bumps [tailwindcss-rails](https://github.com/rails/tailwindcss-rails) from 2.0.32 to 2.0.33.
- [Release notes](https://github.com/rails/tailwindcss-rails/releases)
- [Changelog](https://github.com/rails/tailwindcss-rails/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rails/tailwindcss-rails/compare/v2.0.32...v2.0.33)
---
updated-dependencies:
- dependency-name: tailwindcss-rails
dependency-type: direct:development
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
Gemfile.lock | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Gemfile.lock b/Gemfile.lock
index 5e3c05cbf70..0f60547d34a 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -619,7 +619,7 @@ GEM
attr_required (>= 0.0.5)
faraday (~> 2.0)
faraday-follow_redirects
- tailwindcss-rails (2.0.32)
+ tailwindcss-rails (2.0.33)
railties (>= 6.0.0)
terser (1.1.20)
execjs (>= 0.3.0, < 3)
From 2cf9d8822ce3b2fd21685c569765cdc9f19aa60a Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 11 Dec 2023 11:21:43 -0800
Subject: [PATCH 063/112] Bump good_job from 3.21.2 to 3.21.3 (#4279)
Bumps [good_job](https://github.com/bensheldon/good_job) from 3.21.2 to 3.21.3.
- [Release notes](https://github.com/bensheldon/good_job/releases)
- [Changelog](https://github.com/bensheldon/good_job/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bensheldon/good_job/compare/v3.21.2...v3.21.3)
---
updated-dependencies:
- dependency-name: good_job
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
Gemfile.lock | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Gemfile.lock b/Gemfile.lock
index 0f60547d34a..7175ad204d4 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -241,7 +241,7 @@ GEM
ffi (~> 1.0)
globalid (1.2.1)
activesupport (>= 6.1)
- good_job (3.21.2)
+ good_job (3.21.3)
activejob (>= 6.0.0)
activerecord (>= 6.0.0)
concurrent-ruby (>= 1.0.2)
From fdbbcb591181717449ac80901e5641cc2c0f7e19 Mon Sep 17 00:00:00 2001
From: Samuel Giddins
Date: Mon, 11 Dec 2023 12:21:12 -0800
Subject: [PATCH 064/112] Fix creating rubygem trusted publisher when gh action
exists (#4282)
Fix is removing repository_owner_id from search params
---
.../oidc/trusted_publisher/github_action.rb | 3 ++-
...ygem_trusted_publishers_controller_test.rb | 19 +++++++++++++++++++
2 files changed, 21 insertions(+), 1 deletion(-)
diff --git a/app/models/oidc/trusted_publisher/github_action.rb b/app/models/oidc/trusted_publisher/github_action.rb
index 29873689123..0a3280ee41c 100644
--- a/app/models/oidc/trusted_publisher/github_action.rb
+++ b/app/models/oidc/trusted_publisher/github_action.rb
@@ -42,8 +42,9 @@ def self.permitted_attributes
end
def self.build_trusted_publisher(params)
- params.delete(:environment) if params[:environment].blank?
params = params.reverse_merge(repository_owner_id: nil, repository_name: nil, workflow_filename: nil, environment: nil)
+ params.delete(:environment) if params[:environment].blank?
+ params.delete(:repository_owner_id)
find_or_initialize_by(params)
end
diff --git a/test/integration/oidc/rubygem_trusted_publishers_controller_test.rb b/test/integration/oidc/rubygem_trusted_publishers_controller_test.rb
index d695739859e..4f8ab977973 100644
--- a/test/integration/oidc/rubygem_trusted_publishers_controller_test.rb
+++ b/test/integration/oidc/rubygem_trusted_publishers_controller_test.rb
@@ -88,6 +88,25 @@ class OIDC::RubygemTrustedPublishersControllerTest < ActionDispatch::Integration
assert_redirected_to rubygem_trusted_publishers_url(@rubygem.slug)
end
+ should "create rubygem trusted publisher when trusted publisher already exists" do
+ stub_request(:get, "https://api.github.com/users/example")
+ .to_return(status: 200, body: { id: "123456" }.to_json, headers: { "Content-Type" => "application/json" })
+
+ github_action_trusted_publisher = create(:oidc_trusted_publisher_github_action)
+
+ assert_difference("OIDC::RubygemTrustedPublisher.count") do
+ post rubygem_trusted_publishers_url(@rubygem.slug), params: {
+ oidc_rubygem_trusted_publisher: {
+ trusted_publisher_type: github_action_trusted_publisher.class.polymorphic_name,
+ trusted_publisher_attributes: github_action_trusted_publisher.as_json
+ .slice("workflow_filename", "repository_owner", "repository_name").merge("environment" => "")
+ }
+ }
+ end
+
+ assert_redirected_to rubygem_trusted_publishers_url(@rubygem.slug)
+ end
+
should "error creating trusted publisher with type" do
assert_no_difference("OIDC::RubygemTrustedPublisher.count") do
post rubygem_trusted_publishers_url(@rubygem.slug), params: {
From 64b78de6ff766772cef911399172e4eb609891f6 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 12 Dec 2023 09:14:22 -0800
Subject: [PATCH 065/112] Bump ruby/setup-ruby from 1.161.0 to 1.162.0 (#4288)
---
.github/workflows/lint.yml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index e1f94598ee4..4e8bbee38b5 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- - uses: ruby/setup-ruby@8575951200e472d5f2d95c625da0c7bec8217c42 # v1.161.0
+ - uses: ruby/setup-ruby@af848b40be8bb463a751551a1180d74782ba8a72 # v1.162.0
with:
bundler-cache: true
- name: Rubocop
@@ -23,7 +23,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- - uses: ruby/setup-ruby@8575951200e472d5f2d95c625da0c7bec8217c42 # v1.161.0
+ - uses: ruby/setup-ruby@af848b40be8bb463a751551a1180d74782ba8a72 # v1.162.0
with:
bundler-cache: true
- name: Brakeman
@@ -41,7 +41,7 @@ jobs:
- name: login to Github Packages
run: echo "${{ github.token }}" | docker login https://ghcr.io -u ${GITHUB_ACTOR} --password-stdin
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- - uses: ruby/setup-ruby@8575951200e472d5f2d95c625da0c7bec8217c42 # v1.161.0
+ - uses: ruby/setup-ruby@af848b40be8bb463a751551a1180d74782ba8a72 # v1.162.0
with:
bundler-cache: true
- name: krane render
From 11f7a16bf28951ec85bd31d9a316568091a27b4e Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 12 Dec 2023 09:14:58 -0800
Subject: [PATCH 066/112] Bump good_job from 3.21.3 to 3.21.5 (#4287)
---
Gemfile.lock | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Gemfile.lock b/Gemfile.lock
index 7175ad204d4..5113d6bde0b 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -241,7 +241,7 @@ GEM
ffi (~> 1.0)
globalid (1.2.1)
activesupport (>= 6.1)
- good_job (3.21.3)
+ good_job (3.21.5)
activejob (>= 6.0.0)
activerecord (>= 6.0.0)
concurrent-ruby (>= 1.0.2)
From bc0a321250d21ef66446cd29901899cb53678c19 Mon Sep 17 00:00:00 2001
From: Samuel Giddins
Date: Tue, 12 Dec 2023 16:40:45 -0800
Subject: [PATCH 067/112] Make pending publisher link visible to everyone on
settings#edit
---
app/views/settings/edit.html.erb | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/app/views/settings/edit.html.erb b/app/views/settings/edit.html.erb
index 066b24bd8c0..15f23bb0ee4 100644
--- a/app/views/settings/edit.html.erb
+++ b/app/views/settings/edit.html.erb
@@ -69,12 +69,10 @@
<%= link_to t('api_keys.index.api_keys'), profile_api_keys_path %>
-<% if @user.oidc_pending_trusted_publishers.any? %>
-