diff options
author | ThibG <thib@sitedethib.com> | 2020-06-09 13:00:24 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-06-09 13:00:24 +0200 |
commit | 3287a10fe9658a6c370431a925abc50d4f68ea08 (patch) | |
tree | 88a408a943aa421a6ba844d309f673150cd92434 /app/models | |
parent | f328f2faa3fbdb182921366c6a20e745c069b840 (diff) | |
parent | e9d7ca7645fc37534df1222e8d7ab8f3d533b852 (diff) |
Merge pull request #1350 from ThibG/glitch-soc/merge-upstream
Merge upstream changes
Diffstat (limited to 'app/models')
-rw-r--r-- | app/models/account.rb | 1 | ||||
-rw-r--r-- | app/models/concerns/account_associations.rb | 1 | ||||
-rw-r--r-- | app/models/device.rb | 35 | ||||
-rw-r--r-- | app/models/encrypted_message.rb | 50 | ||||
-rw-r--r-- | app/models/message_franking.rb | 19 | ||||
-rw-r--r-- | app/models/one_time_key.rb | 21 | ||||
-rw-r--r-- | app/models/preview_card.rb | 9 | ||||
-rw-r--r-- | app/models/system_key.rb | 41 | ||||
-rw-r--r-- | app/models/user.rb | 28 |
9 files changed, 203 insertions, 2 deletions
diff --git a/app/models/account.rb b/app/models/account.rb index 5038d4768..0b3c48543 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -49,6 +49,7 @@ # hide_collections :boolean # avatar_storage_schema_version :integer # header_storage_schema_version :integer +# devices_url :string # class Account < ApplicationRecord diff --git a/app/models/concerns/account_associations.rb b/app/models/concerns/account_associations.rb index 499edbf4e..cca3a17fa 100644 --- a/app/models/concerns/account_associations.rb +++ b/app/models/concerns/account_associations.rb @@ -9,6 +9,7 @@ module AccountAssociations # Identity proofs has_many :identity_proofs, class_name: 'AccountIdentityProof', dependent: :destroy, inverse_of: :account + has_many :devices, dependent: :destroy, inverse_of: :account # Timelines has_many :statuses, inverse_of: :account, dependent: :destroy diff --git a/app/models/device.rb b/app/models/device.rb new file mode 100644 index 000000000..97d0d2774 --- /dev/null +++ b/app/models/device.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true +# == Schema Information +# +# Table name: devices +# +# id :bigint(8) not null, primary key +# access_token_id :bigint(8) +# account_id :bigint(8) +# device_id :string default(""), not null +# name :string default(""), not null +# fingerprint_key :text default(""), not null +# identity_key :text default(""), not null +# created_at :datetime not null +# updated_at :datetime not null +# + +class Device < ApplicationRecord + belongs_to :access_token, class_name: 'Doorkeeper::AccessToken' + belongs_to :account + + has_many :one_time_keys, dependent: :destroy, inverse_of: :device + has_many :encrypted_messages, dependent: :destroy, inverse_of: :device + + validates :name, :fingerprint_key, :identity_key, presence: true + validates :fingerprint_key, :identity_key, ed25519_key: true + + before_save :invalidate_associations, if: -> { device_id_changed? || fingerprint_key_changed? || identity_key_changed? } + + private + + def invalidate_associations + one_time_keys.destroy_all + encrypted_messages.destroy_all + end +end diff --git a/app/models/encrypted_message.rb b/app/models/encrypted_message.rb new file mode 100644 index 000000000..5e0aba434 --- /dev/null +++ b/app/models/encrypted_message.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true +# == Schema Information +# +# Table name: encrypted_messages +# +# id :bigint(8) not null, primary key +# device_id :bigint(8) +# from_account_id :bigint(8) +# from_device_id :string default(""), not null +# type :integer default(0), not null +# body :text default(""), not null +# digest :text default(""), not null +# message_franking :text default(""), not null +# created_at :datetime not null +# updated_at :datetime not null +# + +class EncryptedMessage < ApplicationRecord + self.inheritance_column = nil + + include Paginable + + scope :up_to, ->(id) { where(arel_table[:id].lteq(id)) } + + belongs_to :device + belongs_to :from_account, class_name: 'Account' + + around_create Mastodon::Snowflake::Callbacks + + after_commit :push_to_streaming_api + + private + + def push_to_streaming_api + Rails.logger.info(streaming_channel) + Rails.logger.info(subscribed_to_timeline?) + + return if destroyed? || !subscribed_to_timeline? + + PushEncryptedMessageWorker.perform_async(id) + end + + def subscribed_to_timeline? + Redis.current.exists("subscribed:#{streaming_channel}") + end + + def streaming_channel + "timeline:#{device.account_id}:#{device.device_id}" + end +end diff --git a/app/models/message_franking.rb b/app/models/message_franking.rb new file mode 100644 index 000000000..c72bd1cca --- /dev/null +++ b/app/models/message_franking.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class MessageFranking + attr_reader :hmac, :source_account_id, :target_account_id, + :timestamp, :original_franking + + def initialize(attributes = {}) + @hmac = attributes[:hmac] + @source_account_id = attributes[:source_account_id] + @target_account_id = attributes[:target_account_id] + @timestamp = attributes[:timestamp] + @original_franking = attributes[:original_franking] + end + + def to_token + crypt = ActiveSupport::MessageEncryptor.new(SystemKey.current_key, serializer: Oj) + crypt.encrypt_and_sign(self) + end +end diff --git a/app/models/one_time_key.rb b/app/models/one_time_key.rb new file mode 100644 index 000000000..8ada34824 --- /dev/null +++ b/app/models/one_time_key.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true +# == Schema Information +# +# Table name: one_time_keys +# +# id :bigint(8) not null, primary key +# device_id :bigint(8) +# key_id :string default(""), not null +# key :text default(""), not null +# signature :text default(""), not null +# created_at :datetime not null +# updated_at :datetime not null +# + +class OneTimeKey < ApplicationRecord + belongs_to :device + + validates :key_id, :key, :signature, presence: true + validates :key, ed25519_key: true + validates :signature, ed25519_signature: { message: :key, verify_key: ->(one_time_key) { one_time_key.device.fingerprint_key } } +end diff --git a/app/models/preview_card.rb b/app/models/preview_card.rb index 2802f4667..235928260 100644 --- a/app/models/preview_card.rb +++ b/app/models/preview_card.rb @@ -23,19 +23,25 @@ # updated_at :datetime not null # embed_url :string default(""), not null # image_storage_schema_version :integer +# blurhash :string # class PreviewCard < ApplicationRecord IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif'].freeze LIMIT = 1.megabytes + BLURHASH_OPTIONS = { + x_comp: 4, + y_comp: 4, + }.freeze + self.inheritance_column = false enum type: [:link, :photo, :video, :rich] has_and_belongs_to_many :statuses - has_attached_file :image, styles: ->(f) { image_styles(f) }, convert_options: { all: '-quality 80 -strip' } + has_attached_file :image, processors: [:thumbnail, :blurhash_transcoder], styles: ->(f) { image_styles(f) }, convert_options: { all: '-quality 80 -strip' } include Attachmentable @@ -72,6 +78,7 @@ class PreviewCard < ApplicationRecord geometry: '400x400>', file_geometry_parser: FastGeometryParser, convert_options: '-coalesce -strip', + blurhash: BLURHASH_OPTIONS, }, } diff --git a/app/models/system_key.rb b/app/models/system_key.rb new file mode 100644 index 000000000..f17db7c2d --- /dev/null +++ b/app/models/system_key.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: system_keys +# +# id :bigint(8) not null, primary key +# key :binary +# created_at :datetime not null +# updated_at :datetime not null +# +class SystemKey < ApplicationRecord + ROTATION_PERIOD = 1.week.freeze + + before_validation :set_key + + scope :expired, ->(now = Time.now.utc) { where(arel_table[:created_at].lt(now - ROTATION_PERIOD * 3)) } + + class << self + def current_key + previous_key = order(id: :asc).last + + if previous_key && previous_key.created_at >= ROTATION_PERIOD.ago + previous_key.key + else + create.key + end + end + end + + private + + def set_key + return if key.present? + + cipher = OpenSSL::Cipher.new('AES-256-GCM') + cipher.encrypt + + self.key = cipher.random_key + end +end diff --git a/app/models/user.rb b/app/models/user.rb index c8dbd2fd3..a05d98d88 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -38,6 +38,8 @@ # chosen_languages :string is an Array # created_by_application_id :bigint(8) # approved :boolean default(TRUE), not null +# sign_in_token :string +# sign_in_token_sent_at :datetime # class User < ApplicationRecord @@ -114,7 +116,7 @@ class User < ApplicationRecord :default_content_type, :system_emoji_font, to: :settings, prefix: :setting, allow_nil: false - attr_reader :invite_code + attr_reader :invite_code, :sign_in_token_attempt attr_writer :external def confirmed? @@ -168,6 +170,10 @@ class User < ApplicationRecord true end + def suspicious_sign_in?(ip) + !otp_required_for_login? && current_sign_in_at.present? && current_sign_in_at < 2.weeks.ago && !recent_ip?(ip) + end + def functional? confirmed? && approved? && !disabled? && !account.suspended? end @@ -270,6 +276,13 @@ class User < ApplicationRecord super end + def external_or_valid_password?(compare_password) + # If encrypted_password is blank, we got the user from LDAP or PAM, + # so credentials are already valid + + encrypted_password.blank? || valid_password?(compare_password) + end + def send_reset_password_instructions return false if encrypted_password.blank? @@ -305,6 +318,15 @@ class User < ApplicationRecord end end + def sign_in_token_expired? + sign_in_token_sent_at.nil? || sign_in_token_sent_at < 5.minutes.ago + end + + def generate_sign_in_token + self.sign_in_token = Devise.friendly_token(6) + self.sign_in_token_sent_at = Time.now.utc + end + protected def send_devise_notification(notification, *args) @@ -321,6 +343,10 @@ class User < ApplicationRecord private + def recent_ip?(ip) + recent_ips.any? { |(_, recent_ip)| recent_ip == ip } + end + def send_pending_devise_notifications pending_devise_notifications.each do |notification, args| render_and_send_devise_message(notification, *args) |