Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/api/app/models/event/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def notification_events
Event::CommentForRequest, Event::CommentForReport,
Event::RelationshipCreate, Event::RelationshipDelete,
Event::Report, Event::Decision, Event::AppealCreated,
Event::WorkflowRunFail,
Event::WorkflowRunFail, Event::TokenDisabled,
Event::AddedUserToGroup, Event::RemovedUserFromGroup,
Event::Assignment]
end
Expand Down
56 changes: 56 additions & 0 deletions src/api/app/models/event/token_disabled.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
module Event
class TokenDisabled < Base
self.description = 'SCM/CI Token disabled due to authorization failure'
payload_keys :id, :token_id, :scm_vendor, :summary, :token_description

receiver_roles :token_executor, :token_member
delegate :members, to: :token, prefix: true

self.notification_explanation = 'Receive notifications when an SCM/CI integration token is disabled due to authorization problems.'

# Example of subject:
# GitHub workflow token disabled
def subject
vendor_map = { 'github' => 'GitHub', 'gitlab' => 'GitLab', 'gitea' => 'Gitea' }
vendor = vendor_map[payload['scm_vendor']] || payload['scm_vendor']&.capitalize || 'SCM'
"#{vendor} workflow token disabled"
end

def token_executors
[token&.executor].compact
end

def parameters_for_notification
super.merge(notifiable_type: 'Token::Workflow', notifiable_id: payload['token_id'], type: 'NotificationToken')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@geetxnshgoyal here you add the parameters for the type: 'NotificationToken, but there is no corresponding model for this notification type. You need to add a model for it, similar to https://github.com/openSUSE/open-build-service/blob/master/src/api/app/models/notification_workflow_run.rb

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@krauselukas thanks for telling that! I've now added the NotificationToken model in [notification_token.rb] with all the required methods also i filled the data migration to create the default subscriptions the model should now handle notification rendering properly!!

end

def event_object
Token.find_by(id: payload['token_id'])
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The event_object method searches for any Token type, but this event is specific to workflow tokens. This is inconsistent with the token private method (line 33-34) which correctly searches for Token::Workflow. This could return the wrong token type if a non-workflow token has the same ID.

Consider changing this to:

def event_object
  Token.find_by(id: payload['token_id'], type: 'Token::Workflow')
end

Or reuse the existing private method:

def event_object
  token
end
Suggested change
Token.find_by(id: payload['token_id'])
token

Copilot uses AI. Check for mistakes.
end

private

def token
Token.find_by(id: payload['token_id'], type: 'Token::Workflow')
end
end
end

# == Schema Information
#
# Table name: events
#
# id :bigint not null, primary key
# eventtype :string(255) not null, indexed
# mails_sent :boolean default(FALSE), indexed
# payload :text(16777215)
# undone_jobs :integer default(0)
# created_at :datetime indexed
# updated_at :datetime
#
# Indexes
#
# index_events_on_created_at (created_at)
# index_events_on_eventtype (eventtype)
# index_events_on_mails_sent (mails_sent)
#
58 changes: 58 additions & 0 deletions src/api/app/models/notification_token.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
class NotificationToken < Notification
def description
"Token #{notifiable.description.presence || ''} was disabled".strip
end

def excerpt
event_payload['summary'] || 'Token was disabled due to authorization failure'
end

def avatar_objects
[notifiable&.executor].compact
end

def link_text
'Token'
end

def link_path
return if notifiable.blank?

Rails.application.routes.url_helpers.token_path(notifiable)
end
end

# == Schema Information
#
# Table name: notifications
#
# id :bigint not null, primary key
# bs_request_oldstate :string(255)
# bs_request_state :string(255)
# delivered :boolean default(FALSE), indexed
# event_payload :text(16777215) not null
# event_type :string(255) not null, indexed
# last_seen_at :datetime
# notifiable_type :string(255) indexed => [notifiable_id]
# rss :boolean default(FALSE), indexed
# subscriber_type :string(255) indexed => [subscriber_id]
# subscription_receiver_role :string(255) not null
# title :string(255)
# type :string(255) indexed
# web :boolean default(FALSE), indexed
# created_at :datetime not null, indexed
# updated_at :datetime not null
# notifiable_id :integer indexed => [notifiable_type]
# subscriber_id :integer indexed => [subscriber_type]
#
# Indexes
#
# index_notifications_on_created_at (created_at)
# index_notifications_on_delivered (delivered)
# index_notifications_on_event_type (event_type)
# index_notifications_on_notifiable_type_and_notifiable_id (notifiable_type,notifiable_id)
# index_notifications_on_rss (rss)
# index_notifications_on_subscriber_type_and_subscriber_id (subscriber_type,subscriber_id)
# index_notifications_on_type (type)
# index_notifications_on_web (web)
#
11 changes: 10 additions & 1 deletion src/api/app/models/workflow_run.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,16 @@ def update_as_failed(message)
# "Failed to report back to GitHub: Unauthorized request. Please check your credentials again."
# "Failed to report back to GitHub: Request is forbidden."

token.update(enabled: false) if message.include?('Unauthorized request') || /Request (is )?forbidden/.match?(message)
return unless message.include?('Unauthorized request') || /Request (is )?forbidden/.match?(message)

token.update(enabled: false)
# Create event notification for token being disabled
Event::TokenDisabled.create(
token_id: token.id,
scm_vendor: scm_vendor,
summary: message,
token_description: token.description
)
end

# Stores debug info to help figure out what went wrong when trying to save a Status in the SCM.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true

class CreateDefaultTokenDisabledSubscriptions < ActiveRecord::Migration[7.2]
def up
# Create default subscriptions for Event::TokenDisabled
# This event is triggered when a workflow token is disabled due to authorization failures
create_default_subscription('token_executor', :instant_email)
create_default_subscription('token_executor', :web)
create_default_subscription('token_member', :instant_email)
create_default_subscription('token_member', :web)
end

def down
raise ActiveRecord::IrreversibleMigration
end

private

def create_default_subscription(receiver_role, channel)
EventSubscription.find_or_create_by!(
eventtype: 'Event::TokenDisabled',
receiver_role: receiver_role,
channel: channel,
user_id: nil,
group_id: nil
) do |subscription|
subscription.enabled = true
end
end
end
159 changes: 159 additions & 0 deletions src/api/spec/models/event/token_disabled_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
RSpec.describe Event::TokenDisabled do
describe '#token_executors' do
subject { event.token_executors }

let(:token) { create(:workflow_token) }
let(:event) do
Event::TokenDisabled.create(
token_id: token.id,
scm_vendor: 'github',
summary: 'Failed to report back to GitHub: Unauthorized request.',
token_description: 'My workflow token'
)
end

it { expect(subject).to contain_exactly(token.executor) }

context 'when the token does not exist' do
before do
event
token.destroy
end

it { expect(subject).to be_empty }
end
end
Comment on lines +1 to +25
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test coverage is incomplete for Event::TokenDisabled. Missing tests for:

  • parameters_for_notification method (line 23-25)
  • event_object method (line 27-29)
  • token_members receiver role functionality (line 7)

Consider adding tests to verify these methods work correctly, especially the parameters_for_notification method which sets up the notification creation with the correct type and notifiable.

Copilot uses AI. Check for mistakes.

describe '#subject' do
subject { event.subject }

let(:token) { create(:workflow_token) }

context 'with GitHub vendor' do
let(:event) do
Event::TokenDisabled.create(
token_id: token.id,
scm_vendor: 'github',
summary: 'Failed to report back to GitHub: Unauthorized request.',
token_description: 'My workflow token'
)
end

it { expect(subject).to eq('GitHub workflow token disabled') }
end

context 'with GitLab vendor' do
let(:event) do
Event::TokenDisabled.create(
token_id: token.id,
scm_vendor: 'gitlab',
summary: 'Failed to report back to GitLab: Request forbidden.',
token_description: 'My workflow token'
)
end

it { expect(subject).to eq('GitLab workflow token disabled') }
end

context 'with nil vendor' do
let(:event) do
Event::TokenDisabled.create(
token_id: token.id,
scm_vendor: nil,
summary: 'Failed to report back.',
token_description: 'My workflow token'
)
end

it { expect(subject).to eq('SCM workflow token disabled') }
end

context 'with unknown vendor' do
let(:event) do
Event::TokenDisabled.create(
token_id: token.id,
scm_vendor: 'bitbucket',
summary: 'Failed to report back to Bitbucket.',
token_description: 'My workflow token'
)
end

it { expect(subject).to eq('Bitbucket workflow token disabled') }
end
end

describe '#parameters_for_notification' do
subject { event.parameters_for_notification }

let(:token) { create(:workflow_token) }
let(:event) do
Event::TokenDisabled.create(
token_id: token.id,
scm_vendor: 'github',
summary: 'Failed to report back to GitHub: Unauthorized request.',
token_description: 'My workflow token'
)
end

it 'includes the correct notifiable type' do
expect(subject[:notifiable_type]).to eq('Token::Workflow')
end

it 'includes the correct notifiable id' do
expect(subject[:notifiable_id]).to eq(token.id)
end

it 'includes the correct notification type' do
expect(subject[:type]).to eq('NotificationToken')
end
end

describe '#event_object' do
subject { event.event_object }

let(:token) { create(:workflow_token) }
let(:event) do
Event::TokenDisabled.create(
token_id: token.id,
scm_vendor: 'github',
summary: 'Failed to report back to GitHub: Unauthorized request.',
token_description: 'My workflow token'
)
end

it { expect(subject).to eq(token) }

context 'when the token does not exist' do
before do
event
token.destroy
end

it { expect(subject).to be_nil }
end
end

describe '#token_members' do
subject { event.token_members }

let(:executor) { create(:confirmed_user) }
let(:member) { create(:confirmed_user) }
let(:token) { create(:workflow_token, executor: executor) }
let(:event) do
Event::TokenDisabled.create(
token_id: token.id,
scm_vendor: 'github',
summary: 'Failed to report back to GitHub: Unauthorized request.',
token_description: 'My workflow token'
)
end

before do
token.users << member
end

it 'returns all members including shared users' do
expect(subject).to include(member)
end
end
end
Loading