mirror of
https://github.com/mastodon/mastodon.git
synced 2025-07-12 15:33:14 +00:00
Compare commits
2 Commits
f46dbef477
...
8021229257
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8021229257 | ||
![]() |
4393bd395a |
|
@ -4,7 +4,7 @@ class Api::V1::AppsController < Api::BaseController
|
||||||
skip_before_action :require_authenticated_user!
|
skip_before_action :require_authenticated_user!
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@app = Doorkeeper::Application.create!(application_options)
|
@app = OAuth::Application.create!(application_options)
|
||||||
render json: @app, serializer: REST::CredentialApplicationSerializer
|
render json: @app, serializer: REST::CredentialApplicationSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ class OAuth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
Web::PushSubscription.unsubscribe_for(params[:id], current_resource_owner)
|
Web::PushSubscription.unsubscribe_for(params[:id], current_resource_owner)
|
||||||
Doorkeeper::Application.find_by(id: params[:id])&.close_streaming_sessions(current_resource_owner)
|
OAuth::Application.find_by(id: params[:id])&.close_streaming_sessions(current_resource_owner)
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ class Settings::ApplicationsController < Settings::BaseController
|
||||||
def show; end
|
def show; end
|
||||||
|
|
||||||
def new
|
def new
|
||||||
@application = Doorkeeper::Application.new(
|
@application = OAuth::Application.new(
|
||||||
redirect_uri: Doorkeeper.configuration.native_redirect_uri,
|
redirect_uri: Doorkeeper.configuration.native_redirect_uri,
|
||||||
scopes: 'profile'
|
scopes: 'profile'
|
||||||
)
|
)
|
||||||
|
@ -59,6 +59,6 @@ class Settings::ApplicationsController < Settings::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def application_params
|
def application_params
|
||||||
params.expect(doorkeeper_application: [:name, :redirect_uri, :website, scopes: []])
|
params.expect(oauth_application: [:name, :redirect_uri, :website, scopes: []])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module AccessGrantExtension
|
|
||||||
extend ActiveSupport::Concern
|
|
||||||
|
|
||||||
included do
|
|
||||||
scope :expired, -> { where.not(expires_in: nil).where('created_at + MAKE_INTERVAL(secs => expires_in) < NOW()') }
|
|
||||||
scope :revoked, -> { where.not(revoked_at: nil).where(revoked_at: ...Time.now.utc) }
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,29 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module AccessTokenExtension
|
|
||||||
extend ActiveSupport::Concern
|
|
||||||
|
|
||||||
included do
|
|
||||||
include Redisable
|
|
||||||
|
|
||||||
has_many :web_push_subscriptions, class_name: 'Web::PushSubscription', inverse_of: :access_token
|
|
||||||
|
|
||||||
after_commit :push_to_streaming_api
|
|
||||||
|
|
||||||
scope :expired, -> { where.not(expires_in: nil).where('created_at + MAKE_INTERVAL(secs => expires_in) < NOW()') }
|
|
||||||
scope :not_revoked, -> { where(revoked_at: nil) }
|
|
||||||
scope :revoked, -> { where.not(revoked_at: nil).where(revoked_at: ...Time.now.utc) }
|
|
||||||
end
|
|
||||||
|
|
||||||
def revoke(clock = Time)
|
|
||||||
update(revoked_at: clock.now.utc)
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_last_used(request, clock = Time)
|
|
||||||
update(last_used_at: clock.now.utc, last_used_ip: request.remote_ip)
|
|
||||||
end
|
|
||||||
|
|
||||||
def push_to_streaming_api
|
|
||||||
redis.publish("timeline:access_token:#{id}", Oj.dump(event: :kill)) if revoked? || destroyed?
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,49 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module ApplicationExtension
|
|
||||||
extend ActiveSupport::Concern
|
|
||||||
|
|
||||||
APP_NAME_LIMIT = 60
|
|
||||||
APP_REDIRECT_URI_LIMIT = 2_000
|
|
||||||
APP_WEBSITE_LIMIT = 2_000
|
|
||||||
|
|
||||||
included do
|
|
||||||
include Redisable
|
|
||||||
|
|
||||||
has_many :created_users, class_name: 'User', foreign_key: 'created_by_application_id', inverse_of: :created_by_application
|
|
||||||
|
|
||||||
validates :name, length: { maximum: APP_NAME_LIMIT }
|
|
||||||
validates :redirect_uri, length: { maximum: APP_REDIRECT_URI_LIMIT }
|
|
||||||
validates :website, url: true, length: { maximum: APP_WEBSITE_LIMIT }, if: :website?
|
|
||||||
|
|
||||||
# The relationship used between Applications and AccessTokens is using
|
|
||||||
# dependent: delete_all, which means the ActiveRecord callback in
|
|
||||||
# AccessTokenExtension is not run, so instead we manually announce to
|
|
||||||
# streaming that these tokens are being deleted.
|
|
||||||
before_destroy :close_streaming_sessions, prepend: true
|
|
||||||
end
|
|
||||||
|
|
||||||
def confirmation_redirect_uri
|
|
||||||
redirect_uri.lines.first.strip
|
|
||||||
end
|
|
||||||
|
|
||||||
def redirect_uris
|
|
||||||
# Doorkeeper stores the redirect_uri value as a newline delimeted list in
|
|
||||||
# the database:
|
|
||||||
redirect_uri.split
|
|
||||||
end
|
|
||||||
|
|
||||||
def close_streaming_sessions(resource_owner = nil)
|
|
||||||
# TODO: #28793 Combine into a single topic
|
|
||||||
payload = Oj.dump(event: :kill)
|
|
||||||
scope = access_tokens
|
|
||||||
scope = scope.where(resource_owner_id: resource_owner.id) unless resource_owner.nil?
|
|
||||||
scope.in_batches do |tokens|
|
|
||||||
redis.pipelined do |pipeline|
|
|
||||||
tokens.ids.each do |id|
|
|
||||||
pipeline.publish("timeline:access_token:#{id}", payload)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -9,12 +9,12 @@ class Vacuum::AccessTokensVacuum
|
||||||
private
|
private
|
||||||
|
|
||||||
def vacuum_revoked_access_tokens!
|
def vacuum_revoked_access_tokens!
|
||||||
Doorkeeper::AccessToken.expired.in_batches.delete_all
|
OAuth::AccessToken.expired.in_batches.delete_all
|
||||||
Doorkeeper::AccessToken.revoked.in_batches.delete_all
|
OAuth::AccessToken.revoked.in_batches.delete_all
|
||||||
end
|
end
|
||||||
|
|
||||||
def vacuum_revoked_access_grants!
|
def vacuum_revoked_access_grants!
|
||||||
Doorkeeper::AccessGrant.expired.in_batches.delete_all
|
OAuth::AccessGrant.expired.in_batches.delete_all
|
||||||
Doorkeeper::AccessGrant.revoked.in_batches.delete_all
|
OAuth::AccessGrant.revoked.in_batches.delete_all
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
7
app/models/oauth.rb
Normal file
7
app/models/oauth.rb
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module OAuth
|
||||||
|
def self.table_name_prefix
|
||||||
|
'oauth_'
|
||||||
|
end
|
||||||
|
end
|
8
app/models/oauth/access_grant.rb
Normal file
8
app/models/oauth/access_grant.rb
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class OAuth::AccessGrant < ApplicationRecord
|
||||||
|
include ::Doorkeeper::Orm::ActiveRecord::Mixins::AccessGrant
|
||||||
|
|
||||||
|
scope :expired, -> { where.not(expires_in: nil).where('created_at + MAKE_INTERVAL(secs => expires_in) < NOW()') }
|
||||||
|
scope :revoked, -> { where.not(revoked_at: nil).where(revoked_at: ...Time.now.utc) }
|
||||||
|
end
|
29
app/models/oauth/access_token.rb
Normal file
29
app/models/oauth/access_token.rb
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class OAuth::AccessToken < ApplicationRecord
|
||||||
|
include ::Doorkeeper::Orm::ActiveRecord::Mixins::AccessToken
|
||||||
|
|
||||||
|
include Redisable
|
||||||
|
|
||||||
|
has_many :web_push_subscriptions, class_name: 'Web::PushSubscription', inverse_of: :access_token, dependent: nil
|
||||||
|
|
||||||
|
scope :expired, -> { where.not(expires_in: nil).where('created_at + MAKE_INTERVAL(secs => expires_in) < NOW()') }
|
||||||
|
scope :not_revoked, -> { where(revoked_at: nil) }
|
||||||
|
scope :revoked, -> { where.not(revoked_at: nil).where(revoked_at: ...Time.now.utc) }
|
||||||
|
|
||||||
|
after_commit :push_to_streaming_api
|
||||||
|
|
||||||
|
def revoke(clock = Time)
|
||||||
|
update(revoked_at: clock.now.utc)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_last_used(request, clock = Time)
|
||||||
|
update(last_used_at: clock.now.utc, last_used_ip: request.remote_ip)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def push_to_streaming_api
|
||||||
|
redis.publish("timeline:access_token:#{id}", Oj.dump(event: :kill)) if revoked? || destroyed?
|
||||||
|
end
|
||||||
|
end
|
44
app/models/oauth/application.rb
Normal file
44
app/models/oauth/application.rb
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class OAuth::Application < ApplicationRecord
|
||||||
|
include ::Doorkeeper::Orm::ActiveRecord::Mixins::Application
|
||||||
|
|
||||||
|
APP_NAME_LIMIT = 60
|
||||||
|
APP_REDIRECT_URI_LIMIT = 2_000
|
||||||
|
APP_WEBSITE_LIMIT = 2_000
|
||||||
|
|
||||||
|
include Redisable
|
||||||
|
|
||||||
|
has_many :created_users, class_name: 'User', foreign_key: :created_by_application_id, inverse_of: :created_by_application, dependent: nil
|
||||||
|
|
||||||
|
validates :name, length: { maximum: APP_NAME_LIMIT }
|
||||||
|
validates :redirect_uri, length: { maximum: APP_REDIRECT_URI_LIMIT }
|
||||||
|
validates :website, url: true, length: { maximum: APP_WEBSITE_LIMIT }, if: :website?
|
||||||
|
|
||||||
|
before_destroy :close_streaming_sessions, prepend: true
|
||||||
|
|
||||||
|
def confirmation_redirect_uri
|
||||||
|
redirect_uri.lines.first.strip
|
||||||
|
end
|
||||||
|
|
||||||
|
def redirect_uris
|
||||||
|
# The redirect_uri value is stored as a newline delimited list
|
||||||
|
redirect_uri.split
|
||||||
|
end
|
||||||
|
|
||||||
|
# The association between `Application` and `AccessToken` uses a setting of
|
||||||
|
# `dependent: delete_all` which means the callbacks in `AccessToken` are not
|
||||||
|
# run. Instead, announce to streaming that these tokens are being deleted.
|
||||||
|
def close_streaming_sessions(resource_owner = nil)
|
||||||
|
payload = Oj.dump(event: :kill)
|
||||||
|
scope = access_tokens
|
||||||
|
scope = scope.where(resource_owner_id: resource_owner.id) unless resource_owner.nil?
|
||||||
|
scope.in_batches do |tokens|
|
||||||
|
redis.pipelined do |pipeline|
|
||||||
|
tokens.ids.each do |id|
|
||||||
|
pipeline.publish("timeline:access_token:#{id}", payload)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -32,7 +32,7 @@ class Report < ApplicationRecord
|
||||||
rate_limit by: :account, family: :reports
|
rate_limit by: :account, family: :reports
|
||||||
|
|
||||||
belongs_to :account
|
belongs_to :account
|
||||||
belongs_to :application, class_name: 'Doorkeeper::Application', optional: true
|
belongs_to :application, class_name: 'OAuth::Application', optional: true
|
||||||
|
|
||||||
with_options class_name: 'Account' do
|
with_options class_name: 'Account' do
|
||||||
belongs_to :target_account
|
belongs_to :target_account
|
||||||
|
|
|
@ -19,7 +19,7 @@ class SessionActivation < ApplicationRecord
|
||||||
include BrowserDetection
|
include BrowserDetection
|
||||||
|
|
||||||
belongs_to :user, inverse_of: :session_activations
|
belongs_to :user, inverse_of: :session_activations
|
||||||
belongs_to :access_token, class_name: 'Doorkeeper::AccessToken', dependent: :destroy, optional: true
|
belongs_to :access_token, class_name: 'OAuth::AccessToken', dependent: :destroy, optional: true
|
||||||
belongs_to :web_push_subscription, class_name: 'Web::PushSubscription', dependent: :destroy, optional: true
|
belongs_to :web_push_subscription, class_name: 'Web::PushSubscription', dependent: :destroy, optional: true
|
||||||
|
|
||||||
delegate :token,
|
delegate :token,
|
||||||
|
@ -61,12 +61,12 @@ class SessionActivation < ApplicationRecord
|
||||||
private
|
private
|
||||||
|
|
||||||
def assign_access_token
|
def assign_access_token
|
||||||
self.access_token = Doorkeeper::AccessToken.create!(access_token_attributes)
|
self.access_token = OAuth::AccessToken.create!(access_token_attributes)
|
||||||
end
|
end
|
||||||
|
|
||||||
def access_token_attributes
|
def access_token_attributes
|
||||||
{
|
{
|
||||||
application_id: Doorkeeper::Application.find_by(superapp: true)&.id,
|
application_id: OAuth::Application.find_by(superapp: true)&.id,
|
||||||
resource_owner_id: user_id,
|
resource_owner_id: user_id,
|
||||||
scopes: DEFAULT_SCOPES.join(' '),
|
scopes: DEFAULT_SCOPES.join(' '),
|
||||||
expires_in: Doorkeeper.configuration.access_token_expires_in,
|
expires_in: Doorkeeper.configuration.access_token_expires_in,
|
||||||
|
|
|
@ -64,7 +64,7 @@ class Status < ApplicationRecord
|
||||||
update_index('statuses', :proper)
|
update_index('statuses', :proper)
|
||||||
update_index('public_statuses', :proper)
|
update_index('public_statuses', :proper)
|
||||||
|
|
||||||
belongs_to :application, class_name: 'Doorkeeper::Application', optional: true
|
belongs_to :application, class_name: 'OAuth::Application', optional: true
|
||||||
|
|
||||||
belongs_to :account, inverse_of: :statuses
|
belongs_to :account, inverse_of: :statuses
|
||||||
belongs_to :in_reply_to_account, class_name: 'Account', optional: true
|
belongs_to :in_reply_to_account, class_name: 'Account', optional: true
|
||||||
|
|
|
@ -83,11 +83,11 @@ class User < ApplicationRecord
|
||||||
|
|
||||||
belongs_to :account, inverse_of: :user
|
belongs_to :account, inverse_of: :user
|
||||||
belongs_to :invite, counter_cache: :uses, optional: true
|
belongs_to :invite, counter_cache: :uses, optional: true
|
||||||
belongs_to :created_by_application, class_name: 'Doorkeeper::Application', optional: true
|
belongs_to :created_by_application, class_name: 'OAuth::Application', optional: true
|
||||||
belongs_to :role, class_name: 'UserRole', optional: true
|
belongs_to :role, class_name: 'UserRole', optional: true
|
||||||
accepts_nested_attributes_for :account
|
accepts_nested_attributes_for :account
|
||||||
|
|
||||||
has_many :applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: nil
|
has_many :applications, class_name: 'OAuth::Application', as: :owner, dependent: nil
|
||||||
has_many :backups, inverse_of: :user, dependent: nil
|
has_many :backups, inverse_of: :user, dependent: nil
|
||||||
has_many :invites, inverse_of: :user, dependent: nil
|
has_many :invites, inverse_of: :user, dependent: nil
|
||||||
has_many :markers, inverse_of: :user, dependent: :destroy
|
has_many :markers, inverse_of: :user, dependent: :destroy
|
||||||
|
@ -303,7 +303,7 @@ class User < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def applications_last_used
|
def applications_last_used
|
||||||
Doorkeeper::AccessToken
|
OAuth::AccessToken
|
||||||
.where(resource_owner_id: id)
|
.where(resource_owner_id: id)
|
||||||
.where.not(last_used_at: nil)
|
.where.not(last_used_at: nil)
|
||||||
.group(:application_id)
|
.group(:application_id)
|
||||||
|
@ -314,7 +314,7 @@ class User < ApplicationRecord
|
||||||
def token_for_app(app)
|
def token_for_app(app)
|
||||||
return nil if app.nil? || app.owner != self
|
return nil if app.nil? || app.owner != self
|
||||||
|
|
||||||
Doorkeeper::AccessToken.find_or_create_by(application_id: app.id, resource_owner_id: id) do |t|
|
OAuth::AccessToken.find_or_create_by(application_id: app.id, resource_owner_id: id) do |t|
|
||||||
t.scopes = app.scopes
|
t.scopes = app.scopes
|
||||||
t.expires_in = Doorkeeper.configuration.access_token_expires_in
|
t.expires_in = Doorkeeper.configuration.access_token_expires_in
|
||||||
t.use_refresh_token = Doorkeeper.configuration.refresh_token_enabled?
|
t.use_refresh_token = Doorkeeper.configuration.refresh_token_enabled?
|
||||||
|
@ -368,9 +368,9 @@ class User < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def revoke_access!
|
def revoke_access!
|
||||||
Doorkeeper::AccessGrant.by_resource_owner(self).touch_all(:revoked_at)
|
OAuth::AccessGrant.by_resource_owner(self).touch_all(:revoked_at)
|
||||||
|
|
||||||
Doorkeeper::AccessToken.by_resource_owner(self).in_batches do |batch|
|
OAuth::AccessToken.by_resource_owner(self).in_batches do |batch|
|
||||||
batch.touch_all(:revoked_at)
|
batch.touch_all(:revoked_at)
|
||||||
Web::PushSubscription.where(access_token_id: batch).delete_all
|
Web::PushSubscription.where(access_token_id: batch).delete_all
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
class Web::PushSubscription < ApplicationRecord
|
class Web::PushSubscription < ApplicationRecord
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
belongs_to :access_token, class_name: 'Doorkeeper::AccessToken'
|
belongs_to :access_token, class_name: 'OAuth::AccessToken'
|
||||||
|
|
||||||
has_one :session_activation, foreign_key: 'web_push_subscription_id', inverse_of: :web_push_subscription, dependent: nil
|
has_one :session_activation, foreign_key: 'web_push_subscription_id', inverse_of: :web_push_subscription, dependent: nil
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ class Web::PushSubscription < ApplicationRecord
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def unsubscribe_for(application_id, resource_owner)
|
def unsubscribe_for(application_id, resource_owner)
|
||||||
access_token_ids = Doorkeeper::AccessToken.where(application_id: application_id, resource_owner_id: resource_owner.id).not_revoked.pluck(:id)
|
access_token_ids = OAuth::AccessToken.where(application_id: application_id, resource_owner_id: resource_owner.id).not_revoked.pluck(:id)
|
||||||
where(access_token_id: access_token_ids).delete_all
|
where(access_token_id: access_token_ids).delete_all
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -27,7 +27,7 @@ class AppSignUpService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_access_token!
|
def create_access_token!
|
||||||
@access_token = Doorkeeper::AccessToken.create!(
|
@access_token = OAuth::AccessToken.create!(
|
||||||
application: @app,
|
application: @app,
|
||||||
resource_owner_id: @user.id,
|
resource_owner_id: @user.id,
|
||||||
scopes: @app.scopes,
|
scopes: @app.scopes,
|
||||||
|
|
|
@ -26,7 +26,7 @@ class PostStatusService < BaseService
|
||||||
# @option [String] :scheduled_at
|
# @option [String] :scheduled_at
|
||||||
# @option [Hash] :poll Optional poll to attach
|
# @option [Hash] :poll Optional poll to attach
|
||||||
# @option [Enumerable] :media_ids Optional array of media IDs to attach
|
# @option [Enumerable] :media_ids Optional array of media IDs to attach
|
||||||
# @option [Doorkeeper::Application] :application
|
# @option [OAuth::Application] :application
|
||||||
# @option [String] :idempotency Optional idempotency key
|
# @option [String] :idempotency Optional idempotency key
|
||||||
# @option [Boolean] :with_rate_limit
|
# @option [Boolean] :with_rate_limit
|
||||||
# @option [Enumerable] :allowed_mentions Optional array of expected mentioned account IDs, raises `UnexpectedMentionsError` if unexpected accounts end up in mentions
|
# @option [Enumerable] :allowed_mentions Optional array of expected mentioned account IDs, raises `UnexpectedMentionsError` if unexpected accounts end up in mentions
|
||||||
|
|
|
@ -21,7 +21,7 @@ class PublishScheduledStatusWorker
|
||||||
|
|
||||||
def options_with_objects(options)
|
def options_with_objects(options)
|
||||||
options.tap do |options_hash|
|
options.tap do |options_hash|
|
||||||
options_hash[:application] = Doorkeeper::Application.find(options_hash.delete(:application_id)) if options[:application_id]
|
options_hash[:application] = OAuth::Application.find(options_hash.delete(:application_id)) if options[:application_id]
|
||||||
options_hash[:thread] = Status.find(options_hash.delete(:in_reply_to_id)) if options_hash[:in_reply_to_id]
|
options_hash[:thread] = Status.find(options_hash.delete(:in_reply_to_id)) if options_hash[:in_reply_to_id]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -20,7 +20,7 @@ class Scheduler::IpCleanupScheduler
|
||||||
SessionActivation.where(updated_at: ...IP_RETENTION_PERIOD.ago).in_batches.update_all(ip: nil)
|
SessionActivation.where(updated_at: ...IP_RETENTION_PERIOD.ago).in_batches.update_all(ip: nil)
|
||||||
User.where(current_sign_in_at: ...IP_RETENTION_PERIOD.ago).in_batches.update_all(sign_up_ip: nil)
|
User.where(current_sign_in_at: ...IP_RETENTION_PERIOD.ago).in_batches.update_all(sign_up_ip: nil)
|
||||||
LoginActivity.where(created_at: ...IP_RETENTION_PERIOD.ago).in_batches.destroy_all
|
LoginActivity.where(created_at: ...IP_RETENTION_PERIOD.ago).in_batches.destroy_all
|
||||||
Doorkeeper::AccessToken.where(last_used_at: ...IP_RETENTION_PERIOD.ago).in_batches.update_all(last_used_ip: nil)
|
OAuth::AccessToken.where(last_used_at: ...IP_RETENTION_PERIOD.ago).in_batches.update_all(last_used_ip: nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
def clean_expired_ip_blocks!
|
def clean_expired_ip_blocks!
|
||||||
|
|
|
@ -117,9 +117,6 @@ module Mastodon
|
||||||
end
|
end
|
||||||
|
|
||||||
config.to_prepare do
|
config.to_prepare do
|
||||||
Doorkeeper::Application.include ApplicationExtension
|
|
||||||
Doorkeeper::AccessGrant.include AccessGrantExtension
|
|
||||||
Doorkeeper::AccessToken.include AccessTokenExtension
|
|
||||||
Devise::FailureApp.include AbstractController::Callbacks
|
Devise::FailureApp.include AbstractController::Callbacks
|
||||||
Devise::FailureApp.include Localized
|
Devise::FailureApp.include Localized
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,6 +4,10 @@ Doorkeeper.configure do
|
||||||
# Change the ORM that doorkeeper will use (needs plugins)
|
# Change the ORM that doorkeeper will use (needs plugins)
|
||||||
orm :active_record
|
orm :active_record
|
||||||
|
|
||||||
|
access_grant_class 'OAuth::AccessGrant'
|
||||||
|
access_token_class 'OAuth::AccessToken'
|
||||||
|
application_class 'OAuth::Application'
|
||||||
|
|
||||||
# This block will be called to check whether the resource owner is authenticated or not.
|
# This block will be called to check whether the resource owner is authenticated or not.
|
||||||
resource_owner_authenticator do
|
resource_owner_authenticator do
|
||||||
current_user || redirect_to(new_user_session_url)
|
current_user || redirect_to(new_user_session_url)
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
Doorkeeper::Application.create_with(name: 'Web', redirect_uri: Doorkeeper.configuration.native_redirect_uri, scopes: 'read write follow push').find_or_create_by(superapp: true)
|
OAuth::Application.create_with(name: 'Web', redirect_uri: Doorkeeper.configuration.native_redirect_uri, scopes: 'read write follow push').find_or_create_by(superapp: true)
|
||||||
|
|
|
@ -134,12 +134,12 @@ namespace :tests do
|
||||||
exit(1)
|
exit(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
unless Doorkeeper::Application.find(2)[:scopes] == 'write:accounts profile'
|
unless OAuth::Application.find(2)[:scopes] == 'write:accounts profile'
|
||||||
puts 'Application OAuth scopes not rewritten as expected'
|
puts 'Application OAuth scopes not rewritten as expected'
|
||||||
exit(1)
|
exit(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
unless Doorkeeper::Application.find(2).access_tokens.first[:scopes] == 'write:accounts profile'
|
unless OAuth::Application.find(2).access_tokens.first[:scopes] == 'write:accounts profile'
|
||||||
puts 'OAuth access token scopes not rewritten as expected'
|
puts 'OAuth access token scopes not rewritten as expected'
|
||||||
exit(1)
|
exit(1)
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,7 +5,7 @@ require 'rails_helper'
|
||||||
RSpec.describe OAuth::AuthorizationsController do
|
RSpec.describe OAuth::AuthorizationsController do
|
||||||
render_views
|
render_views
|
||||||
|
|
||||||
let(:app) { Doorkeeper::Application.create!(name: 'test', redirect_uri: 'http://localhost/', scopes: 'read') }
|
let(:app) { OAuth::Application.create!(name: 'test', redirect_uri: 'http://localhost/', scopes: 'read') }
|
||||||
|
|
||||||
describe 'GET #new' do
|
describe 'GET #new' do
|
||||||
subject do
|
subject do
|
||||||
|
@ -34,7 +34,7 @@ RSpec.describe OAuth::AuthorizationsController do
|
||||||
|
|
||||||
context 'when app is already authorized' do
|
context 'when app is already authorized' do
|
||||||
before do
|
before do
|
||||||
Doorkeeper::AccessToken.find_or_create_for(
|
OAuth::AccessToken.find_or_create_for(
|
||||||
application: app,
|
application: app,
|
||||||
resource_owner: user.id,
|
resource_owner: user.id,
|
||||||
scopes: app.scopes,
|
scopes: app.scopes,
|
||||||
|
|
|
@ -55,7 +55,7 @@ RSpec.describe OAuth::AuthorizedApplicationsController do
|
||||||
it 'revokes access tokens for the application and removes subscriptions and sends kill payload to streaming' do
|
it 'revokes access tokens for the application and removes subscriptions and sends kill payload to streaming' do
|
||||||
post :destroy, params: { id: application.id }
|
post :destroy, params: { id: application.id }
|
||||||
|
|
||||||
expect(Doorkeeper::AccessToken.where(application: application).first.revoked_at)
|
expect(OAuth::AccessToken.where(application: application).first.revoked_at)
|
||||||
.to_not be_nil
|
.to_not be_nil
|
||||||
expect(Web::PushSubscription.where(user: user).count)
|
expect(Web::PushSubscription.where(user: user).count)
|
||||||
.to eq(0)
|
.to eq(0)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
Fabricator :access_grant, from: 'Doorkeeper::AccessGrant' do
|
Fabricator :access_grant, from: OAuth::AccessGrant do
|
||||||
application
|
application
|
||||||
resource_owner_id { Fabricate(:user).id }
|
resource_owner_id { Fabricate(:user).id }
|
||||||
expires_in 3_600
|
expires_in 3_600
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
Fabricator :access_token, from: 'Doorkeeper::AccessToken'
|
Fabricator :access_token, from: OAuth::AccessToken
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
Fabricator(:application, from: Doorkeeper::Application) do
|
Fabricator(:application, from: OAuth::Application) do
|
||||||
name 'Example'
|
name 'Example'
|
||||||
website 'http://example.com'
|
website 'http://example.com'
|
||||||
redirect_uri 'http://example.com/callback'
|
redirect_uri 'http://example.com/callback'
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe Doorkeeper::AccessGrant do
|
RSpec.describe OAuth::AccessGrant do
|
||||||
describe 'Validations' do
|
describe 'Validations' do
|
||||||
subject { Fabricate :access_grant }
|
subject { Fabricate :access_grant }
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe Doorkeeper::AccessToken do
|
RSpec.describe OAuth::AccessToken do
|
||||||
describe 'Associations' do
|
describe 'Associations' do
|
||||||
it { is_expected.to have_many(:web_push_subscriptions).class_name('Web::PushSubscription').inverse_of(:access_token) }
|
it { is_expected.to have_many(:web_push_subscriptions).class_name('Web::PushSubscription').inverse_of(:access_token) }
|
||||||
end
|
end
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe Doorkeeper::Application do
|
RSpec.describe OAuth::Application do
|
||||||
describe 'Associations' do
|
describe 'Associations' do
|
||||||
it { is_expected.to have_many(:created_users).class_name('User').inverse_of(:created_by_application).with_foreign_key(:created_by_application_id) }
|
it { is_expected.to have_many(:created_users).class_name('User').inverse_of(:created_by_application).with_foreign_key(:created_by_application_id) }
|
||||||
end
|
end
|
|
@ -439,7 +439,7 @@ RSpec.describe User do
|
||||||
|
|
||||||
it 'creates and returns a persisted token' do
|
it 'creates and returns a persisted token' do
|
||||||
expect { user.token_for_app(app) }
|
expect { user.token_for_app(app) }
|
||||||
.to change(Doorkeeper::AccessToken.where(resource_owner_id: user.id, application: app), :count).by(1)
|
.to change(OAuth::AccessToken.where(resource_owner_id: user.id, application: app), :count).by(1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -449,7 +449,7 @@ RSpec.describe User do
|
||||||
|
|
||||||
it 'returns a persisted token' do
|
it 'returns a persisted token' do
|
||||||
expect(user.token_for_app(app))
|
expect(user.token_for_app(app))
|
||||||
.to be_a(Doorkeeper::AccessToken)
|
.to be_a(OAuth::AccessToken)
|
||||||
.and eq(token)
|
.and eq(token)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -531,7 +531,7 @@ RSpec.describe User do
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_active_user_tokens
|
def remove_active_user_tokens
|
||||||
change { Doorkeeper::AccessToken.active_for(user).count }.to(0)
|
change { OAuth::AccessToken.active_for(user).count }.to(0)
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_user_web_subscriptions
|
def remove_user_web_subscriptions
|
||||||
|
|
|
@ -31,7 +31,7 @@ RSpec.describe 'Apps' do
|
||||||
expect(response.content_type)
|
expect(response.content_type)
|
||||||
.to start_with('application/json')
|
.to start_with('application/json')
|
||||||
|
|
||||||
app = Doorkeeper::Application.find_by(name: client_name)
|
app = OAuth::Application.find_by(name: client_name)
|
||||||
|
|
||||||
expect(app).to be_present
|
expect(app).to be_present
|
||||||
expect(app.scopes.to_s).to eq scopes
|
expect(app.scopes.to_s).to eq scopes
|
||||||
|
@ -64,7 +64,7 @@ RSpec.describe 'Apps' do
|
||||||
expect(response).to have_http_status(200)
|
expect(response).to have_http_status(200)
|
||||||
expect(response.content_type)
|
expect(response.content_type)
|
||||||
.to start_with('application/json')
|
.to start_with('application/json')
|
||||||
expect(Doorkeeper::Application.find_by(name: client_name)).to be_present
|
expect(OAuth::Application.find_by(name: client_name)).to be_present
|
||||||
|
|
||||||
expect(response.parsed_body)
|
expect(response.parsed_body)
|
||||||
.to include(
|
.to include(
|
||||||
|
@ -84,7 +84,7 @@ RSpec.describe 'Apps' do
|
||||||
expect(response.content_type)
|
expect(response.content_type)
|
||||||
.to start_with('application/json')
|
.to start_with('application/json')
|
||||||
|
|
||||||
app = Doorkeeper::Application.find_by(name: client_name)
|
app = OAuth::Application.find_by(name: client_name)
|
||||||
|
|
||||||
expect(app).to be_present
|
expect(app).to be_present
|
||||||
expect(app.scopes.to_s).to eq 'read'
|
expect(app.scopes.to_s).to eq 'read'
|
||||||
|
@ -117,12 +117,12 @@ RSpec.describe 'Apps' do
|
||||||
expect(response).to have_http_status(200)
|
expect(response).to have_http_status(200)
|
||||||
expect(response.content_type)
|
expect(response.content_type)
|
||||||
.to start_with('application/json')
|
.to start_with('application/json')
|
||||||
expect(Doorkeeper::Application.find_by(name: client_name).scopes.to_s).to eq 'read'
|
expect(OAuth::Application.find_by(name: client_name).scopes.to_s).to eq 'read'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with a too-long name' do
|
context 'with a too-long name' do
|
||||||
let(:client_name) { 'a' * Doorkeeper::Application::APP_NAME_LIMIT * 2 }
|
let(:client_name) { 'a' * OAuth::Application::APP_NAME_LIMIT * 2 }
|
||||||
|
|
||||||
it 'returns http unprocessable entity' do
|
it 'returns http unprocessable entity' do
|
||||||
subject
|
subject
|
||||||
|
@ -134,7 +134,7 @@ RSpec.describe 'Apps' do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with a too-long website' do
|
context 'with a too-long website' do
|
||||||
let(:website) { "https://foo.bar/#{'a' * Doorkeeper::Application::APP_WEBSITE_LIMIT * 2}" }
|
let(:website) { "https://foo.bar/#{'a' * OAuth::Application::APP_WEBSITE_LIMIT * 2}" }
|
||||||
|
|
||||||
it 'returns http unprocessable entity' do
|
it 'returns http unprocessable entity' do
|
||||||
subject
|
subject
|
||||||
|
@ -146,7 +146,7 @@ RSpec.describe 'Apps' do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with a too-long redirect_uri' do
|
context 'with a too-long redirect_uri' do
|
||||||
let(:redirect_uris) { "https://app.example/#{'a' * Doorkeeper::Application::APP_REDIRECT_URI_LIMIT * 2}" }
|
let(:redirect_uris) { "https://app.example/#{'a' * OAuth::Application::APP_REDIRECT_URI_LIMIT * 2}" }
|
||||||
|
|
||||||
it 'returns http unprocessable entity' do
|
it 'returns http unprocessable entity' do
|
||||||
subject
|
subject
|
||||||
|
@ -180,7 +180,7 @@ RSpec.describe 'Apps' do
|
||||||
expect(response.content_type)
|
expect(response.content_type)
|
||||||
.to start_with('application/json')
|
.to start_with('application/json')
|
||||||
|
|
||||||
app = Doorkeeper::Application.find_by(name: client_name)
|
app = OAuth::Application.find_by(name: client_name)
|
||||||
|
|
||||||
expect(app).to be_present
|
expect(app).to be_present
|
||||||
expect(app.redirect_uri).to eq redirect_uris
|
expect(app.redirect_uri).to eq redirect_uris
|
||||||
|
@ -204,7 +204,7 @@ RSpec.describe 'Apps' do
|
||||||
expect(response.content_type)
|
expect(response.content_type)
|
||||||
.to start_with('application/json')
|
.to start_with('application/json')
|
||||||
|
|
||||||
app = Doorkeeper::Application.find_by(name: client_name)
|
app = OAuth::Application.find_by(name: client_name)
|
||||||
|
|
||||||
expect(app).to be_present
|
expect(app).to be_present
|
||||||
expect(app.redirect_uri).to eq redirect_uris.join "\n"
|
expect(app.redirect_uri).to eq redirect_uris.join "\n"
|
||||||
|
@ -276,7 +276,7 @@ RSpec.describe 'Apps' do
|
||||||
expect(response.content_type)
|
expect(response.content_type)
|
||||||
.to start_with('application/json')
|
.to start_with('application/json')
|
||||||
|
|
||||||
app = Doorkeeper::Application.find_by(name: client_name)
|
app = OAuth::Application.find_by(name: client_name)
|
||||||
|
|
||||||
expect(app).to be_present
|
expect(app).to be_present
|
||||||
expect(app.website).to eq website
|
expect(app.website).to eq website
|
||||||
|
|
|
@ -169,7 +169,7 @@ RSpec.describe 'Caching behavior' do
|
||||||
|
|
||||||
let(:alice) { Account.find_by(username: 'alice') }
|
let(:alice) { Account.find_by(username: 'alice') }
|
||||||
let(:user) { User.find_by(email: 'user@host.example') }
|
let(:user) { User.find_by(email: 'user@host.example') }
|
||||||
let(:token) { Doorkeeper::AccessToken.find_by(resource_owner_id: user.id) }
|
let(:token) { OAuth::AccessToken.find_by(resource_owner_id: user.id) }
|
||||||
|
|
||||||
before_all do
|
before_all do
|
||||||
alice = Fabricate(:account, username: 'alice')
|
alice = Fabricate(:account, username: 'alice')
|
||||||
|
|
|
@ -25,7 +25,7 @@ RSpec.describe 'Settings / Exports' do
|
||||||
|
|
||||||
let(:params) do
|
let(:params) do
|
||||||
{
|
{
|
||||||
doorkeeper_application: {
|
oauth_application: {
|
||||||
name: 'My New App',
|
name: 'My New App',
|
||||||
redirect_uri: 'urn:ietf:wg:oauth:2.0:oob',
|
redirect_uri: 'urn:ietf:wg:oauth:2.0:oob',
|
||||||
website: 'http://google.com',
|
website: 'http://google.com',
|
||||||
|
@ -36,13 +36,13 @@ RSpec.describe 'Settings / Exports' do
|
||||||
|
|
||||||
it 'supports passing scope values as string' do
|
it 'supports passing scope values as string' do
|
||||||
expect { subject }
|
expect { subject }
|
||||||
.to change(Doorkeeper::Application, :count).by(1)
|
.to change(OAuth::Application, :count).by(1)
|
||||||
expect(response)
|
expect(response)
|
||||||
.to redirect_to(settings_applications_path)
|
.to redirect_to(settings_applications_path)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'gracefully handles invalid nested params' do
|
it 'gracefully handles invalid nested params' do
|
||||||
post settings_applications_path(doorkeeper_application: 'invalid')
|
post settings_applications_path(oauth_application: 'invalid')
|
||||||
|
|
||||||
expect(response)
|
expect(response)
|
||||||
.to have_http_status(400)
|
.to have_http_status(400)
|
||||||
|
@ -53,7 +53,7 @@ RSpec.describe 'Settings / Exports' do
|
||||||
let(:application) { Fabricate :application, owner: user }
|
let(:application) { Fabricate :application, owner: user }
|
||||||
|
|
||||||
it 'gracefully handles invalid nested params' do
|
it 'gracefully handles invalid nested params' do
|
||||||
put settings_application_path(application.id, doorkeeper_application: 'invalid')
|
put settings_application_path(application.id, oauth_application: 'invalid')
|
||||||
|
|
||||||
expect(response)
|
expect(response)
|
||||||
.to have_http_status(400)
|
.to have_http_status(400)
|
||||||
|
|
|
@ -68,7 +68,7 @@ RSpec.describe 'Auth Passwords' do
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_token_count
|
def user_token_count
|
||||||
Doorkeeper::AccessToken
|
OAuth::AccessToken
|
||||||
.active_for(user)
|
.active_for(user)
|
||||||
.count
|
.count
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,7 +7,7 @@ RSpec.describe 'Using OAuth from an external app' do
|
||||||
|
|
||||||
subject { visit "/oauth/authorize?#{params.to_query}" }
|
subject { visit "/oauth/authorize?#{params.to_query}" }
|
||||||
|
|
||||||
let(:client_app) { Doorkeeper::Application.create!(name: 'test', redirect_uri: about_url(host: Rails.application.config.x.local_domain), scopes: 'read') }
|
let(:client_app) { OAuth::Application.create!(name: 'test', redirect_uri: about_url(host: Rails.application.config.x.local_domain), scopes: 'read') }
|
||||||
let(:params) do
|
let(:params) do
|
||||||
{ client_id: client_app.uid, response_type: 'code', redirect_uri: client_app.redirect_uri, scope: 'read' }
|
{ client_id: client_app.uid, response_type: 'code', redirect_uri: client_app.redirect_uri, scope: 'read' }
|
||||||
end
|
end
|
||||||
|
@ -264,7 +264,7 @@ RSpec.describe 'Using OAuth from an external app' do
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_has_grant_with_client_app?
|
def user_has_grant_with_client_app?
|
||||||
Doorkeeper::AccessGrant
|
OAuth::AccessGrant
|
||||||
.exists?(
|
.exists?(
|
||||||
application: client_app,
|
application: client_app,
|
||||||
resource_owner_id: user.id
|
resource_owner_id: user.id
|
||||||
|
|
|
@ -34,7 +34,7 @@ RSpec.describe 'Settings applications page' do
|
||||||
fill_in_form
|
fill_in_form
|
||||||
|
|
||||||
expect { submit_form }
|
expect { submit_form }
|
||||||
.to change(Doorkeeper::Application, :count).by(1)
|
.to change(OAuth::Application, :count).by(1)
|
||||||
expect(page)
|
expect(page)
|
||||||
.to have_content(I18n.t('doorkeeper.applications.index.title'))
|
.to have_content(I18n.t('doorkeeper.applications.index.title'))
|
||||||
.and have_content('My new app')
|
.and have_content('My new app')
|
||||||
|
@ -47,7 +47,7 @@ RSpec.describe 'Settings applications page' do
|
||||||
visit new_settings_application_path
|
visit new_settings_application_path
|
||||||
|
|
||||||
expect { submit_form }
|
expect { submit_form }
|
||||||
.to not_change(Doorkeeper::Application, :count)
|
.to not_change(OAuth::Application, :count)
|
||||||
expect(page)
|
expect(page)
|
||||||
.to have_content("can't be blank")
|
.to have_content("can't be blank")
|
||||||
end
|
end
|
||||||
|
@ -60,9 +60,9 @@ RSpec.describe 'Settings applications page' do
|
||||||
fill_in I18n.t('activerecord.attributes.doorkeeper/application.redirect_uri'),
|
fill_in I18n.t('activerecord.attributes.doorkeeper/application.redirect_uri'),
|
||||||
with: 'urn:ietf:wg:oauth:2.0:oob'
|
with: 'urn:ietf:wg:oauth:2.0:oob'
|
||||||
|
|
||||||
check 'read', id: :doorkeeper_application_scopes_read
|
check 'read', id: :oauth_application_scopes_read
|
||||||
check 'write', id: :doorkeeper_application_scopes_write
|
check 'write', id: :oauth_application_scopes_write
|
||||||
check 'follow', id: :doorkeeper_application_scopes_follow
|
check 'follow', id: :oauth_application_scopes_follow
|
||||||
end
|
end
|
||||||
|
|
||||||
def submit_form
|
def submit_form
|
||||||
|
@ -76,12 +76,12 @@ RSpec.describe 'Settings applications page' do
|
||||||
|
|
||||||
fill_in form_app_name_label,
|
fill_in form_app_name_label,
|
||||||
with: 'My new app name with a new value'
|
with: 'My new app name with a new value'
|
||||||
check 'push', id: :doorkeeper_application_scopes_push
|
check 'push', id: :oauth_application_scopes_push
|
||||||
submit_form
|
submit_form
|
||||||
|
|
||||||
expect(page)
|
expect(page)
|
||||||
.to have_content('My new app name with a new value')
|
.to have_content('My new app name with a new value')
|
||||||
.and have_checked_field('push', id: :doorkeeper_application_scopes_push)
|
.and have_checked_field('push', id: :oauth_application_scopes_push)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not update with wrong values' do
|
it 'does not update with wrong values' do
|
||||||
|
@ -110,7 +110,7 @@ RSpec.describe 'Settings applications page' do
|
||||||
visit settings_applications_path
|
visit settings_applications_path
|
||||||
|
|
||||||
expect { destroy_application }
|
expect { destroy_application }
|
||||||
.to change(Doorkeeper::Application, :count).by(-1)
|
.to change(OAuth::Application, :count).by(-1)
|
||||||
expect(page)
|
expect(page)
|
||||||
.to have_no_content(application.name)
|
.to have_no_content(application.name)
|
||||||
expect(redis_pipeline_stub)
|
expect(redis_pipeline_stub)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user