Merge pull request 'User avatars' (#223) from feature/user_avatars into master
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #223 Reviewed-by: Greg <greg@noreply.kosmos.org>
This commit is contained in:
commit
03be2e09e6
@ -9,13 +9,5 @@ module AppCatalog
|
|||||||
@image_url = image_url_for(web_app.apple_touch_icon)
|
@image_url = image_url_for(web_app.apple_touch_icon)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def image_url_for(attachment)
|
|
||||||
if Setting.s3_enabled?
|
|
||||||
s3_image_url(attachment)
|
|
||||||
else
|
|
||||||
Rails.application.routes.url_helpers.rails_blob_path(attachment, only_path: true)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -22,7 +22,7 @@ class Admin::UsersController < Admin::BaseController
|
|||||||
|
|
||||||
@services_enabled = @user.services_enabled
|
@services_enabled = @user.services_enabled
|
||||||
|
|
||||||
@avatar = LdapManager::FetchAvatar.call(cn: @user.cn)
|
@ldap_avatar = LdapManager::FetchAvatar.call(cn: @user.cn)
|
||||||
end
|
end
|
||||||
|
|
||||||
# POST /admin/users/:username/invitations
|
# POST /admin/users/:username/invitations
|
||||||
|
27
app/controllers/avatars_controller.rb
Normal file
27
app/controllers/avatars_controller.rb
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
class AvatarsController < ApplicationController
|
||||||
|
def show
|
||||||
|
if user = User.find_by(cn: params[:username])
|
||||||
|
http_status :not_found and return unless user.avatar.attached?
|
||||||
|
|
||||||
|
sha256_hash = params[:hash]
|
||||||
|
format = params[:format]&.to_sym || :png
|
||||||
|
# size = params[:size]&.to_sym || :original
|
||||||
|
|
||||||
|
unless user.avatar.filename.to_s == "#{sha256_hash}.#{format}"
|
||||||
|
http_status :not_found and return
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO See note for avatar_variant in user model
|
||||||
|
# blob = if size == :original
|
||||||
|
# user.avatar.blob
|
||||||
|
# else
|
||||||
|
# user.avatar_variant(size: size)&.blob
|
||||||
|
# end
|
||||||
|
|
||||||
|
data = user.avatar.blob.download
|
||||||
|
send_data data, type: "image/#{format}", disposition: "inline"
|
||||||
|
else
|
||||||
|
http_status :not_found
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -8,6 +8,9 @@ class Discourse::SsoController < ApplicationController
|
|||||||
sso.email = current_user.email
|
sso.email = current_user.email
|
||||||
sso.username = current_user.cn
|
sso.username = current_user.cn
|
||||||
sso.name = current_user.display_name
|
sso.name = current_user.display_name
|
||||||
|
if current_user.avatar.attached?
|
||||||
|
sso.avatar_url = helpers.image_url_for(current_user.avatar)
|
||||||
|
end
|
||||||
sso.admin = current_user.is_admin?
|
sso.admin = current_user.is_admin?
|
||||||
sso.sso_secret = secret
|
sso.sso_secret = secret
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ class SettingsController < ApplicationController
|
|||||||
def update
|
def update
|
||||||
@user.preferences.merge!(user_params[:preferences] || {})
|
@user.preferences.merge!(user_params[:preferences] || {})
|
||||||
@user.display_name = user_params[:display_name]
|
@user.display_name = user_params[:display_name]
|
||||||
@user.avatar_new = user_params[:avatar]
|
@user.avatar_new = user_params[:avatar_new]
|
||||||
@user.pgp_pubkey = user_params[:pgp_pubkey]
|
@user.pgp_pubkey = user_params[:pgp_pubkey]
|
||||||
|
|
||||||
if @user.save
|
if @user.save
|
||||||
@ -34,7 +34,12 @@ class SettingsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
if @user.avatar_new.present?
|
if @user.avatar_new.present?
|
||||||
LdapManager::UpdateAvatar.call(dn: @user.dn, file: @user.avatar_new)
|
if store_user_avatar
|
||||||
|
LdapManager::UpdateAvatar.call(user: @user)
|
||||||
|
else
|
||||||
|
@validation_errors = @user.errors
|
||||||
|
render :show, status: :unprocessable_entity and return
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if @user.pgp_pubkey && (@user.pgp_pubkey != @user.ldap_entry[:pgp_key])
|
if @user.pgp_pubkey && (@user.pgp_pubkey != @user.ldap_entry[:pgp_key])
|
||||||
@ -162,7 +167,7 @@ class SettingsController < ApplicationController
|
|||||||
|
|
||||||
def user_params
|
def user_params
|
||||||
params.require(:user).permit(
|
params.require(:user).permit(
|
||||||
:display_name, :avatar, :pgp_pubkey,
|
:display_name, :avatar_new, :pgp_pubkey,
|
||||||
preferences: UserPreferences.pref_keys
|
preferences: UserPreferences.pref_keys
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
@ -184,4 +189,40 @@ class SettingsController < ApplicationController
|
|||||||
salt = BCrypt::Engine.generate_salt
|
salt = BCrypt::Engine.generate_salt
|
||||||
BCrypt::Engine.hash_secret(password, salt)
|
BCrypt::Engine.hash_secret(password, salt)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def store_user_avatar
|
||||||
|
io = @user.avatar_new.tempfile
|
||||||
|
img_data = process_avatar(io)
|
||||||
|
tempfile = Tempfile.create
|
||||||
|
tempfile.binmode
|
||||||
|
tempfile.write(img_data)
|
||||||
|
tempfile.rewind
|
||||||
|
|
||||||
|
hash = Digest::SHA256.hexdigest(img_data)
|
||||||
|
ext = @user.avatar_new.content_type == "image/png" ? "png" : "jpg"
|
||||||
|
filename = "#{hash}.#{ext}"
|
||||||
|
|
||||||
|
if filename == @user.avatar.filename.to_s
|
||||||
|
@user.errors.add(:avatar, "must be a new file/picture")
|
||||||
|
false
|
||||||
|
else
|
||||||
|
key = "users/#{@user.cn}/avatars/#{filename}"
|
||||||
|
@user.avatar.attach io: tempfile, key: key, filename: filename
|
||||||
|
@user.save
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def process_avatar(io)
|
||||||
|
processed = ImageProcessing::Vips
|
||||||
|
.source(io)
|
||||||
|
.resize_to_fill(400, 400)
|
||||||
|
.saver(strip: true)
|
||||||
|
.call
|
||||||
|
io.rewind
|
||||||
|
processed.read
|
||||||
|
rescue Vips::Error => e
|
||||||
|
Sentry.capture_exception(e) if Setting.sentry_enabled?
|
||||||
|
Rails.logger.error { "Image processing failed for avatar: #{e.message}" }
|
||||||
|
nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -33,6 +33,10 @@ class WebfingerController < WellKnownController
|
|||||||
links: []
|
links: []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if @user.avatar.attached?
|
||||||
|
jrd[:links] += avatar_link
|
||||||
|
end
|
||||||
|
|
||||||
if Setting.mastodon_enabled && @user.service_enabled?(:mastodon)
|
if Setting.mastodon_enabled && @user.service_enabled?(:mastodon)
|
||||||
# https://docs.joinmastodon.org/spec/webfinger/
|
# https://docs.joinmastodon.org/spec/webfinger/
|
||||||
jrd[:aliases] += mastodon_aliases
|
jrd[:aliases] += mastodon_aliases
|
||||||
@ -47,6 +51,16 @@ class WebfingerController < WellKnownController
|
|||||||
jrd
|
jrd
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def avatar_link
|
||||||
|
[
|
||||||
|
{
|
||||||
|
rel: "http://webfinger.net/rel/avatar",
|
||||||
|
type: @user.avatar.content_type,
|
||||||
|
href: helpers.image_url_for(@user.avatar)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
def mastodon_aliases
|
def mastodon_aliases
|
||||||
[
|
[
|
||||||
"#{Setting.mastodon_public_url}/@#{@user.cn}",
|
"#{Setting.mastodon_public_url}/@#{@user.cn}",
|
||||||
|
@ -14,4 +14,19 @@ module ApplicationHelper
|
|||||||
def badge(text, color)
|
def badge(text, color)
|
||||||
tag.span text, class: "inline-flex items-center rounded-full bg-#{color}-100 px-2.5 py-0.5 text-xs font-medium text-#{color}-800"
|
tag.span text, class: "inline-flex items-center rounded-full bg-#{color}-100 px-2.5 py-0.5 text-xs font-medium text-#{color}-800"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def image_url_for(attachment)
|
||||||
|
return s3_image_url(attachment) if Setting.s3_enabled?
|
||||||
|
|
||||||
|
if attachment.record.is_a?(User) && attachment.name == "avatar"
|
||||||
|
hash, format = attachment.blob.filename.to_s.split(".", 2)
|
||||||
|
user_avatar_url(
|
||||||
|
username: attachment.record.cn,
|
||||||
|
hash: hash,
|
||||||
|
format: format
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Rails.application.routes.url_helpers.rails_blob_path(attachment, only_path: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -4,7 +4,7 @@ class CreateLdapUserJob < ApplicationJob
|
|||||||
def perform(username:, domain:, email:, hashed_pw:, confirmed: false)
|
def perform(username:, domain:, email:, hashed_pw:, confirmed: false)
|
||||||
dn = "cn=#{username},ou=#{domain},cn=users,dc=kosmos,dc=org"
|
dn = "cn=#{username},ou=#{domain},cn=users,dc=kosmos,dc=org"
|
||||||
attr = {
|
attr = {
|
||||||
objectclass: ["top", "account", "person", "extensibleObject"],
|
objectclass: ["top", "account", "person", "inetOrgPerson", "extensibleObject"],
|
||||||
cn: username,
|
cn: username,
|
||||||
sn: username,
|
sn: username,
|
||||||
uid: username,
|
uid: username,
|
||||||
|
@ -4,8 +4,8 @@ class User < ApplicationRecord
|
|||||||
include EmailValidatable
|
include EmailValidatable
|
||||||
|
|
||||||
attr_accessor :current_password
|
attr_accessor :current_password
|
||||||
attr_accessor :avatar_new
|
|
||||||
attr_accessor :display_name
|
attr_accessor :display_name
|
||||||
|
attr_accessor :avatar_new
|
||||||
attr_accessor :pgp_pubkey
|
attr_accessor :pgp_pubkey
|
||||||
|
|
||||||
serialize :preferences, coder: UserPreferences
|
serialize :preferences, coder: UserPreferences
|
||||||
@ -27,6 +27,12 @@ class User < ApplicationRecord
|
|||||||
|
|
||||||
has_many :accounts, through: :lndhub_user
|
has_many :accounts, through: :lndhub_user
|
||||||
|
|
||||||
|
#
|
||||||
|
# Attachments
|
||||||
|
#
|
||||||
|
|
||||||
|
has_one_attached :avatar
|
||||||
|
|
||||||
#
|
#
|
||||||
# Validations
|
# Validations
|
||||||
#
|
#
|
||||||
@ -50,6 +56,7 @@ class User < ApplicationRecord
|
|||||||
validates_length_of :display_name, minimum: 3, maximum: 35, allow_blank: true,
|
validates_length_of :display_name, minimum: 3, maximum: 35, allow_blank: true,
|
||||||
if: -> { defined?(@display_name) }
|
if: -> { defined?(@display_name) }
|
||||||
|
|
||||||
|
|
||||||
validate :acceptable_avatar
|
validate :acceptable_avatar
|
||||||
|
|
||||||
validate :acceptable_pgp_key_format, if: -> { defined?(@pgp_pubkey) && @pgp_pubkey.present? }
|
validate :acceptable_pgp_key_format, if: -> { defined?(@pgp_pubkey) && @pgp_pubkey.present? }
|
||||||
@ -77,6 +84,10 @@ class User < ApplicationRecord
|
|||||||
:timeoutable,
|
:timeoutable,
|
||||||
:rememberable
|
:rememberable
|
||||||
|
|
||||||
|
#
|
||||||
|
# Methods
|
||||||
|
#
|
||||||
|
|
||||||
def ldap_before_save
|
def ldap_before_save
|
||||||
self.email = Devise::LDAP::Adapter.get_ldap_param(self.cn, "mail").first
|
self.email = Devise::LDAP::Adapter.get_ldap_param(self.cn, "mail").first
|
||||||
self.ou = dn.split(',')
|
self.ou = dn.split(',')
|
||||||
@ -159,6 +170,20 @@ class User < ApplicationRecord
|
|||||||
@display_name ||= ldap_entry[:display_name]
|
@display_name ||= ldap_entry[:display_name]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# TODO Variant keys are currently broken for some reason
|
||||||
|
# (They use the same key as the main blob, when it should be
|
||||||
|
# "/variants/#{key)"
|
||||||
|
# def avatar_variant(size: :medium)
|
||||||
|
# dimensions = case size
|
||||||
|
# when :large then [400, 400]
|
||||||
|
# when :medium then [256, 256]
|
||||||
|
# when :small then [64, 64]
|
||||||
|
# else [256, 256]
|
||||||
|
# end
|
||||||
|
# format = avatar.content_type == "image/png" ? :png : :jpeg
|
||||||
|
# avatar.variant(resize_to_fill: dimensions, format: format)
|
||||||
|
# end
|
||||||
|
|
||||||
def nostr_pubkey
|
def nostr_pubkey
|
||||||
@nostr_pubkey ||= ldap_entry[:nostr_key]
|
@nostr_pubkey ||= ldap_entry[:nostr_key]
|
||||||
end
|
end
|
||||||
@ -186,10 +211,6 @@ class User < ApplicationRecord
|
|||||||
ZBase32.encode(Digest::SHA1.digest(cn))
|
ZBase32.encode(Digest::SHA1.digest(cn))
|
||||||
end
|
end
|
||||||
|
|
||||||
def avatar
|
|
||||||
@avatar_base64 ||= LdapManager::FetchAvatar.call(cn: cn)
|
|
||||||
end
|
|
||||||
|
|
||||||
def services_enabled
|
def services_enabled
|
||||||
ldap_entry[:services_enabled] || []
|
ldap_entry[:services_enabled] || []
|
||||||
end
|
end
|
||||||
@ -227,7 +248,7 @@ class User < ApplicationRecord
|
|||||||
return unless avatar_new.present?
|
return unless avatar_new.present?
|
||||||
|
|
||||||
if avatar_new.size > 1.megabyte
|
if avatar_new.size > 1.megabyte
|
||||||
errors.add(:avatar, "file size is too large")
|
errors.add(:avatar, "must be less than 1MB file size")
|
||||||
end
|
end
|
||||||
|
|
||||||
acceptable_types = ["image/jpeg", "image/png"]
|
acceptable_types = ["image/jpeg", "image/png"]
|
||||||
|
@ -10,7 +10,7 @@ module LdapManager
|
|||||||
filter = Net::LDAP::Filter.eq("cn", @cn)
|
filter = Net::LDAP::Filter.eq("cn", @cn)
|
||||||
|
|
||||||
entry = client.search(base: treebase, filter: filter, attributes: attributes).first
|
entry = client.search(base: treebase, filter: filter, attributes: attributes).first
|
||||||
entry.try(:jpegPhoto) ? entry.jpegPhoto.first : nil
|
entry[:jpegPhoto].present? ? entry.jpegPhoto.first : nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -2,26 +2,41 @@ require "image_processing/vips"
|
|||||||
|
|
||||||
module LdapManager
|
module LdapManager
|
||||||
class UpdateAvatar < LdapManagerService
|
class UpdateAvatar < LdapManagerService
|
||||||
def initialize(dn:, file:)
|
def initialize(user:)
|
||||||
@dn = dn
|
@user = user
|
||||||
@img_data = process(file)
|
@dn = user.dn
|
||||||
end
|
end
|
||||||
|
|
||||||
def call
|
def call
|
||||||
replace_attribute @dn, :jpegPhoto, @img_data
|
unless @user.avatar.attached?
|
||||||
|
Rails.logger.error { "Cannot store empty jpegPhoto for user #{@user.cn}" }
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
img_data = @user.avatar.blob.download
|
||||||
|
jpg_data = process_avatar
|
||||||
|
|
||||||
|
Rails.logger.debug { "Storing new jpegPhoto for user #{@user.cn} in LDAP" }
|
||||||
|
result = replace_attribute(@dn, :jpegPhoto, jpg_data)
|
||||||
|
result == 0
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def process(file)
|
def process_avatar
|
||||||
|
@user.avatar.blob.open do |file|
|
||||||
processed = ImageProcessing::Vips
|
processed = ImageProcessing::Vips
|
||||||
.resize_to_fill(512, 512)
|
|
||||||
.source(file)
|
.source(file)
|
||||||
|
.resize_to_fill(256, 256)
|
||||||
.convert("jpeg")
|
.convert("jpeg")
|
||||||
.saver(strip: true)
|
.saver(strip: true)
|
||||||
.call
|
.call
|
||||||
|
processed.read
|
||||||
Base64.strict_encode64 processed.read
|
end
|
||||||
|
rescue Vips::Error => e
|
||||||
|
Sentry.capture_exception(e) if Setting.sentry_enabled?
|
||||||
|
Rails.logger.error { "Image processing failed for LDAP avatar: #{e.message}" }
|
||||||
|
nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -95,8 +95,8 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Avatar</th>
|
<th>Avatar</th>
|
||||||
<td>
|
<td>
|
||||||
<% if @avatar.present? %>
|
<% if @ldap_avatar.present? %>
|
||||||
<img src="data:image/jpeg;base64,<%= @avatar %>" class="h-48 w-48" />
|
JPEG size: <%= @ldap_avatar.size %>
|
||||||
<% else %>
|
<% else %>
|
||||||
—
|
—
|
||||||
<% end %>
|
<% end %>
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm text-gray-500">
|
<p class="text-sm text-gray-500">
|
||||||
Your user address for Chat and Lightning Network.
|
Your account's address on the Internet
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<%= form_for(@user, url: setting_path(:profile), html: { :method => :put }) do |f| %>
|
<%= form_for(@user, url: setting_path(:profile), html: { :method => :put }) do |f| %>
|
||||||
@ -31,23 +31,19 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if Flipper.enabled?(:avatar_upload, current_user) %>
|
|
||||||
<label class="block">
|
<label class="block">
|
||||||
<p class="font-bold mb-1">
|
<p class="font-bold mb-1">Avatar</p>
|
||||||
Avatar
|
<p class="text-gray-500">Default profile picture</p>
|
||||||
</p>
|
|
||||||
<p class="text-gray-500">
|
|
||||||
Default profile picture
|
|
||||||
</p>
|
|
||||||
<div class="flex items-center gap-6">
|
<div class="flex items-center gap-6">
|
||||||
<% if current_user.avatar.present? %>
|
<% if @user.avatar.attached? %>
|
||||||
<p class="flex-none">
|
<p class="flex-none">
|
||||||
<%= image_tag "data:image/jpeg;base64,#{current_user.avatar}", class: "h-24 w-24 rounded-lg" %>
|
<%= image_tag image_url_for(@user.avatar), class: "h-24 w-24 rounded-lg" %>
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
<div class="grow">
|
<div class="grow">
|
||||||
<p class="mb-2">
|
<p class="mb-2">
|
||||||
<%= f.file_field :avatar, class: "" %>
|
<%= f.file_field :avatar_new, accept: "image/jpeg,image/png" %>
|
||||||
|
</p>
|
||||||
<p class="text-sm text-gray-500">
|
<p class="text-sm text-gray-500">
|
||||||
JPEG or PNG image, not larger than 1 megabyte
|
JPEG or PNG image, not larger than 1 megabyte
|
||||||
</p>
|
</p>
|
||||||
@ -57,7 +53,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<p class="mt-8 pt-6 border-t border-gray-200 text-right">
|
<p class="mt-8 pt-6 border-t border-gray-200 text-right">
|
||||||
<%= f.submit 'Save', class: "btn-md btn-blue w-full md:w-auto" %>
|
<%= f.submit 'Save', class: "btn-md btn-blue w-full md:w-auto" %>
|
||||||
|
@ -15,6 +15,9 @@ Rails.application.routes.draw do
|
|||||||
match 'signup/:step', to: 'signup#steps', as: :signup_steps, via: [:get, :post]
|
match 'signup/:step', to: 'signup#steps', as: :signup_steps, via: [:get, :post]
|
||||||
post 'signup_validate', to: 'signup#validate'
|
post 'signup_validate', to: 'signup#validate'
|
||||||
|
|
||||||
|
|
||||||
|
get "users/:username/avatars/:hash", to: "avatars#show", as: :user_avatar
|
||||||
|
|
||||||
namespace :contributions do
|
namespace :contributions do
|
||||||
root to: 'donations#index'
|
root to: 'donations#index'
|
||||||
resources :donations, only: ['index', 'create'] do
|
resources :donations, only: ['index', 'create'] do
|
||||||
|
@ -2,18 +2,18 @@ require 'rails_helper'
|
|||||||
|
|
||||||
RSpec.describe 'Profile settings', type: :feature do
|
RSpec.describe 'Profile settings', type: :feature do
|
||||||
let(:user) { create :user, cn: "mwahlberg" }
|
let(:user) { create :user, cn: "mwahlberg" }
|
||||||
let(:avatar_base64) { File.read("#{Rails.root}/spec/fixtures/files/avatar-base64.txt") }
|
let(:avatar_jpeg) { File.binread("#{Rails.root}/spec/fixtures/files/taipei.jpg") }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
login_as user, :scope => :user
|
login_as user, :scope => :user
|
||||||
allow(user).to receive(:display_name).and_return("Mark")
|
allow(user).to receive(:display_name).and_return("Mark")
|
||||||
allow_any_instance_of(User).to receive(:dn).and_return("cn=mwahlberg,ou=kosmos.org,cn=users,dc=kosmos,dc=org")
|
|
||||||
allow_any_instance_of(User).to receive(:ldap_entry).and_return({
|
allow_any_instance_of(User).to receive(:dn)
|
||||||
|
.and_return("cn=mwahlberg,ou=kosmos.org,cn=users,dc=kosmos,dc=org")
|
||||||
|
allow_any_instance_of(User).to receive(:ldap_entry)
|
||||||
|
.and_return({
|
||||||
uid: user.cn, ou: user.ou, display_name: "Mark", pgp_key: nil
|
uid: user.cn, ou: user.ou, display_name: "Mark", pgp_key: nil
|
||||||
})
|
})
|
||||||
allow_any_instance_of(User).to receive(:avatar).and_return(avatar_base64)
|
|
||||||
|
|
||||||
Flipper.enable "avatar_upload"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
feature "Update display name" do
|
feature "Update display name" do
|
||||||
@ -57,21 +57,24 @@ RSpec.describe 'Profile settings', type: :feature do
|
|||||||
end
|
end
|
||||||
|
|
||||||
scenario "fails with validation error for file size too large" do
|
scenario "fails with validation error for file size too large" do
|
||||||
|
expect_any_instance_of(LdapManager::UpdateAvatar)
|
||||||
|
.not_to receive(:replace_attribute).and_return(true)
|
||||||
|
|
||||||
visit setting_path(:profile)
|
visit setting_path(:profile)
|
||||||
attach_file "Avatar", "#{Rails.root}/spec/fixtures/files/fsociety-irc.png"
|
attach_file "Avatar", "#{Rails.root}/spec/fixtures/files/fsociety-irc.png"
|
||||||
click_button "Save"
|
click_button "Save"
|
||||||
|
|
||||||
expect(current_url).to eq(setting_url(:profile))
|
expect(current_url).to eq(setting_url(:profile))
|
||||||
within ".error-msg" do
|
within ".error-msg" do
|
||||||
expect(page).to have_content("file size is too large")
|
expect(page).to have_content("must be less than 1MB")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
scenario 'works with valid JPG file' do
|
scenario 'works with valid JPG file' do
|
||||||
file_path = "#{Rails.root}/spec/fixtures/files/taipei.jpg"
|
file_path = "#{Rails.root}/spec/fixtures/files/taipei.jpg"
|
||||||
|
|
||||||
expect_any_instance_of(LdapManager::UpdateAvatar).to receive(:replace_attribute)
|
expect_any_instance_of(LdapManager::UpdateAvatar)
|
||||||
.with(user.dn, :jpegPhoto, avatar_base64).and_return(true)
|
.to receive(:replace_attribute).and_return(true)
|
||||||
|
|
||||||
visit setting_path(:profile)
|
visit setting_path(:profile)
|
||||||
attach_file "Avatar", file_path
|
attach_file "Avatar", file_path
|
||||||
@ -86,7 +89,8 @@ RSpec.describe 'Profile settings', type: :feature do
|
|||||||
scenario 'works with valid PNG file' do
|
scenario 'works with valid PNG file' do
|
||||||
file_path = "#{Rails.root}/spec/fixtures/files/bender.png"
|
file_path = "#{Rails.root}/spec/fixtures/files/bender.png"
|
||||||
|
|
||||||
expect(LdapManager::UpdateAvatar).to receive(:call).and_return(true)
|
expect_any_instance_of(LdapManager::UpdateAvatar)
|
||||||
|
.to receive(:replace_attribute).and_return(true)
|
||||||
|
|
||||||
visit setting_path(:profile)
|
visit setting_path(:profile)
|
||||||
attach_file "Avatar", file_path
|
attach_file "Avatar", file_path
|
||||||
|
@ -32,7 +32,7 @@ RSpec.describe CreateLdapUserJob, type: :job do
|
|||||||
expect(ldap_client_mock).to have_received(:add).with(
|
expect(ldap_client_mock).to have_received(:add).with(
|
||||||
dn: "cn=halfinney,ou=kosmos.org,cn=users,dc=kosmos,dc=org",
|
dn: "cn=halfinney,ou=kosmos.org,cn=users,dc=kosmos,dc=org",
|
||||||
attributes: {
|
attributes: {
|
||||||
objectclass: ["top", "account", "person", "extensibleObject"],
|
objectclass: ["top", "account", "person", "inetOrgPerson", "extensibleObject"],
|
||||||
cn: "halfinney",
|
cn: "halfinney",
|
||||||
sn: "halfinney",
|
sn: "halfinney",
|
||||||
uid: "halfinney",
|
uid: "halfinney",
|
||||||
@ -51,7 +51,7 @@ RSpec.describe CreateLdapUserJob, type: :job do
|
|||||||
expect(ldap_client_mock).to have_received(:add).with(
|
expect(ldap_client_mock).to have_received(:add).with(
|
||||||
dn: "cn=halfinney,ou=kosmos.org,cn=users,dc=kosmos,dc=org",
|
dn: "cn=halfinney,ou=kosmos.org,cn=users,dc=kosmos,dc=org",
|
||||||
attributes: {
|
attributes: {
|
||||||
objectclass: ["top", "account", "person", "extensibleObject"],
|
objectclass: ["top", "account", "person", "inetOrgPerson", "extensibleObject"],
|
||||||
cn: "halfinney",
|
cn: "halfinney",
|
||||||
sn: "halfinney",
|
sn: "halfinney",
|
||||||
uid: "halfinney",
|
uid: "halfinney",
|
||||||
|
@ -18,6 +18,46 @@ RSpec.describe "WebFinger", type: :request do
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "Avatar" do
|
||||||
|
context "not available" do
|
||||||
|
it "does not include an avatar link" do
|
||||||
|
get "/.well-known/webfinger?resource=acct%3Atony%40kosmos.org"
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
res = JSON.parse(response.body)
|
||||||
|
|
||||||
|
link = res["links"].find{|l| l["rel"] == "http://webfinger.net/rel/avatar"}
|
||||||
|
expect(link).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "available" do
|
||||||
|
let(:fixture_path) { Rails.root.join("spec/fixtures/files/taipei.jpg") }
|
||||||
|
let(:img_data) { File.read(fixture_path) }
|
||||||
|
let(:img_hash) { Digest::SHA256.hexdigest(img_data) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
ActiveStorage::Blob.create_and_upload!(
|
||||||
|
io: File.open(fixture_path),
|
||||||
|
filename: "#{img_hash}.jpg",
|
||||||
|
content_type: "image/jpeg"
|
||||||
|
).tap do |blob|
|
||||||
|
user.avatar.attach(blob)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "includes a public avatar link" do
|
||||||
|
get "/.well-known/webfinger?resource=acct%3Atony%40kosmos.org"
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
res = JSON.parse(response.body)
|
||||||
|
|
||||||
|
link = res["links"].find { |l| l["rel"] == "http://webfinger.net/rel/avatar" }
|
||||||
|
expect(link).to be_present
|
||||||
|
expect(link["type"]).to eq("image/jpeg")
|
||||||
|
expect(link["href"]).to match(%r{users/tony/avatars/#{img_hash}.jpg})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "Mastodon entries" do
|
describe "Mastodon entries" do
|
||||||
context "Mastodon available" do
|
context "Mastodon available" do
|
||||||
it "includes the Mastodon aliases and links for the user" do
|
it "includes the Mastodon aliases and links for the user" do
|
||||||
|
Loading…
x
Reference in New Issue
Block a user