Merge pull request #1250 from ThibG/glitch-soc/merge-upstream

Merge upstream changes
This commit is contained in:
ThibG 2019-12-01 13:13:28 +01:00 committed by GitHub
commit 7d59e25fbd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 156 additions and 26 deletions

View File

@ -183,6 +183,9 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
# LDAP_BIND_DN= # LDAP_BIND_DN=
# LDAP_PASSWORD= # LDAP_PASSWORD=
# LDAP_UID=cn # LDAP_UID=cn
# LDAP_UID_CONVERSION_ENABLED=true
# LDAP_UID_CONVERSION_SEARCH=., -
# LDAP_UID_CONVERSION_REPLACE=_
# PAM authentication (optional) # PAM authentication (optional)
# PAM authentication uses for the email generation the "email" pam variable # PAM authentication uses for the email generation the "email" pam variable

View File

@ -204,6 +204,9 @@ STREAMING_CLUSTER_NUM=1
# LDAP_PASSWORD= # LDAP_PASSWORD=
# LDAP_UID=cn # LDAP_UID=cn
# LDAP_SEARCH_FILTER=%{uid}=%{email} # LDAP_SEARCH_FILTER=%{uid}=%{email}
# LDAP_UID_CONVERSION_ENABLED=true
# LDAP_UID_CONVERSION_SEARCH=., -
# LDAP_UID_CONVERSION_REPLACE=_
# PAM authentication (optional) # PAM authentication (optional)
# PAM authentication uses for the email generation the "email" pam variable # PAM authentication uses for the email generation the "email" pam variable

View File

@ -55,7 +55,8 @@ module Admin
params.permit( params.permit(
:account_id, :account_id,
:resolved, :resolved,
:target_account_id :target_account_id,
:by_target_domain
) )
end end

View File

@ -2,7 +2,7 @@
module Admin::FilterHelper module Admin::FilterHelper
ACCOUNT_FILTERS = %i(local remote by_domain active pending silenced suspended username display_name email ip staff).freeze ACCOUNT_FILTERS = %i(local remote by_domain active pending silenced suspended username display_name email ip staff).freeze
REPORT_FILTERS = %i(resolved account_id target_account_id).freeze REPORT_FILTERS = %i(resolved account_id target_account_id by_target_domain).freeze
INVITE_FILTER = %i(available expired).freeze INVITE_FILTER = %i(available expired).freeze
CUSTOM_EMOJI_FILTERS = %i(local remote by_domain shortcode).freeze CUSTOM_EMOJI_FILTERS = %i(local remote by_domain shortcode).freeze
TAGS_FILTERS = %i(directory reviewed unreviewed pending_review popular active name).freeze TAGS_FILTERS = %i(directory reviewed unreviewed pending_review popular active name).freeze

View File

@ -62,15 +62,22 @@ export default class ModalRoot extends React.PureComponent {
} else if (!nextProps.children) { } else if (!nextProps.children) {
this.setState({ revealed: false }); this.setState({ revealed: false });
} }
if (!nextProps.children && !!this.props.children) {
this.activeElement.focus({ preventScroll: true });
this.activeElement = null;
}
} }
componentDidUpdate (prevProps) { componentDidUpdate (prevProps) {
if (!this.props.children && !!prevProps.children) { if (!this.props.children && !!prevProps.children) {
this.getSiblings().forEach(sibling => sibling.removeAttribute('inert')); this.getSiblings().forEach(sibling => sibling.removeAttribute('inert'));
// Because of the wicg-inert polyfill, the activeElement may not be
// immediately selectable, we have to wait for observers to run, as
// described in https://github.com/WICG/inert#performance-and-gotchas
Promise.resolve().then(() => {
this.activeElement.focus({ preventScroll: true });
this.activeElement = null;
}).catch((error) => {
console.error(error);
});
this.handleModalClose(); this.handleModalClose();
} }
if (this.props.children) { if (this.props.children) {

View File

@ -12,6 +12,7 @@ import IconButton from 'flavours/glitch/components/icon_button';
import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp'; import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp';
import { HotKeys } from 'react-hotkeys'; import { HotKeys } from 'react-hotkeys';
import { autoPlayGif } from 'flavours/glitch/util/initial_state'; import { autoPlayGif } from 'flavours/glitch/util/initial_state';
import classNames from 'classnames';
const messages = defineMessages({ const messages = defineMessages({
more: { id: 'status.more', defaultMessage: 'More' }, more: { id: 'status.more', defaultMessage: 'More' },
@ -193,7 +194,7 @@ class Conversation extends ImmutablePureComponent {
return ( return (
<HotKeys handlers={handlers}> <HotKeys handlers={handlers}>
<div className='conversation focusable muted' tabIndex='0'> <div className={classNames('conversation focusable muted', { 'conversation--unread': unread })} tabIndex='0'>
<div className='conversation__avatar'> <div className='conversation__avatar'>
<AvatarComposite accounts={accounts} size={48} /> <AvatarComposite accounts={accounts} size={48} />
</div> </div>
@ -201,7 +202,7 @@ class Conversation extends ImmutablePureComponent {
<div className='conversation__content'> <div className='conversation__content'>
<div className='conversation__content__info'> <div className='conversation__content__info'>
<div className='conversation__content__relative-time'> <div className='conversation__content__relative-time'>
<RelativeTimestamp timestamp={lastStatus.get('created_at')} /> {unread && <span className='conversation__unread' />} <RelativeTimestamp timestamp={lastStatus.get('created_at')} />
</div> </div>
<div className='conversation__content__names' ref={this.setNamesRef}> <div className='conversation__content__names' ref={this.setNamesRef}>

View File

@ -1507,6 +1507,16 @@
flex: 0 0 auto; flex: 0 0 auto;
padding: 10px; padding: 10px;
padding-top: 12px; padding-top: 12px;
position: relative;
}
&__unread {
display: inline-block;
background: $highlight-text-color;
border-radius: 50%;
width: 0.625rem;
height: 0.625rem;
margin: -.1ex .15em .1ex;
} }
&__content { &__content {
@ -1554,6 +1564,22 @@
margin: 0; margin: 0;
} }
} }
&--unread {
background: lighten($ui-base-color, 2%);
&:focus {
background: lighten($ui-base-color, 4%);
}
.conversation__content__info {
font-weight: 700;
}
.conversation__content__relative-time {
color: $primary-text-color;
}
}
} }
.ui .flash-message { .ui .flash-message {

View File

@ -56,15 +56,21 @@ export default class ModalRoot extends React.PureComponent {
} else if (!nextProps.children) { } else if (!nextProps.children) {
this.setState({ revealed: false }); this.setState({ revealed: false });
} }
if (!nextProps.children && !!this.props.children) {
this.activeElement.focus();
this.activeElement = null;
}
} }
componentDidUpdate (prevProps) { componentDidUpdate (prevProps) {
if (!this.props.children && !!prevProps.children) { if (!this.props.children && !!prevProps.children) {
this.getSiblings().forEach(sibling => sibling.removeAttribute('inert')); this.getSiblings().forEach(sibling => sibling.removeAttribute('inert'));
// Because of the wicg-inert polyfill, the activeElement may not be
// immediately selectable, we have to wait for observers to run, as
// described in https://github.com/WICG/inert#performance-and-gotchas
Promise.resolve().then(() => {
this.activeElement.focus();
this.activeElement = null;
}).catch((error) => {
console.error(error);
});
} }
if (this.props.children) { if (this.props.children) {
requestAnimationFrame(() => { requestAnimationFrame(() => {

View File

@ -12,6 +12,7 @@ import IconButton from 'mastodon/components/icon_button';
import RelativeTimestamp from 'mastodon/components/relative_timestamp'; import RelativeTimestamp from 'mastodon/components/relative_timestamp';
import { HotKeys } from 'react-hotkeys'; import { HotKeys } from 'react-hotkeys';
import { autoPlayGif } from 'mastodon/initial_state'; import { autoPlayGif } from 'mastodon/initial_state';
import classNames from 'classnames';
const messages = defineMessages({ const messages = defineMessages({
more: { id: 'status.more', defaultMessage: 'More' }, more: { id: 'status.more', defaultMessage: 'More' },
@ -158,7 +159,7 @@ class Conversation extends ImmutablePureComponent {
return ( return (
<HotKeys handlers={handlers}> <HotKeys handlers={handlers}>
<div className='conversation focusable muted' tabIndex='0'> <div className={classNames('conversation focusable muted', { 'conversation--unread': unread })} tabIndex='0'>
<div className='conversation__avatar'> <div className='conversation__avatar'>
<AvatarComposite accounts={accounts} size={48} /> <AvatarComposite accounts={accounts} size={48} />
</div> </div>
@ -166,7 +167,7 @@ class Conversation extends ImmutablePureComponent {
<div className='conversation__content'> <div className='conversation__content'>
<div className='conversation__content__info'> <div className='conversation__content__info'>
<div className='conversation__content__relative-time'> <div className='conversation__content__relative-time'>
<RelativeTimestamp timestamp={lastStatus.get('created_at')} /> {unread && <span className='conversation__unread' />} <RelativeTimestamp timestamp={lastStatus.get('created_at')} />
</div> </div>
<div className='conversation__content__names' ref={this.setNamesRef}> <div className='conversation__content__names' ref={this.setNamesRef}>

View File

@ -6517,6 +6517,16 @@ noscript {
flex: 0 0 auto; flex: 0 0 auto;
padding: 10px; padding: 10px;
padding-top: 12px; padding-top: 12px;
position: relative;
}
&__unread {
display: inline-block;
background: $highlight-text-color;
border-radius: 50%;
width: 0.625rem;
height: 0.625rem;
margin: -.1ex .15em .1ex;
} }
&__content { &__content {
@ -6564,4 +6574,20 @@ noscript {
word-break: break-word; word-break: break-word;
} }
} }
&--unread {
background: lighten($ui-base-color, 2%);
&:focus {
background: lighten($ui-base-color, 4%);
}
.conversation__content__info {
font-weight: 700;
}
.conversation__content__relative-time {
color: $primary-text-color;
}
}
} }

View File

@ -25,6 +25,14 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
private private
def audience_to
@object['to'] || @json['to']
end
def audience_cc
@object['cc'] || @json['cc']
end
def process_status def process_status
@tags = [] @tags = []
@mentions = [] @mentions = []
@ -75,7 +83,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
end end
def process_audience def process_audience
(as_array(@object['to']) + as_array(@object['cc'])).uniq.each do |audience| (as_array(audience_to) + as_array(audience_cc)).uniq.each do |audience|
next if audience == ActivityPub::TagManager::COLLECTIONS[:public] next if audience == ActivityPub::TagManager::COLLECTIONS[:public]
# Unlike with tags, there is no point in resolving accounts we don't already # Unlike with tags, there is no point in resolving accounts we don't already
@ -291,11 +299,11 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
end end
def visibility_from_audience def visibility_from_audience
if equals_or_includes?(@object['to'], ActivityPub::TagManager::COLLECTIONS[:public]) if equals_or_includes?(audience_to, ActivityPub::TagManager::COLLECTIONS[:public])
:public :public
elsif equals_or_includes?(@object['cc'], ActivityPub::TagManager::COLLECTIONS[:public]) elsif equals_or_includes?(audience_cc, ActivityPub::TagManager::COLLECTIONS[:public])
:unlisted :unlisted
elsif equals_or_includes?(@object['to'], @account.followers_url) elsif equals_or_includes?(audience_to, @account.followers_url)
:private :private
else else
:direct :direct
@ -304,7 +312,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
def audience_includes?(account) def audience_includes?(account)
uri = ActivityPub::TagManager.instance.uri_for(account) uri = ActivityPub::TagManager.instance.uri_for(account)
equals_or_includes?(@object['to'], uri) || equals_or_includes?(@object['cc'], uri) equals_or_includes?(audience_to, uri) || equals_or_includes?(audience_cc, uri)
end end
def replied_to_status def replied_to_status
@ -415,7 +423,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
def addresses_local_accounts? def addresses_local_accounts?
return true if @options[:delivered_to_account_id] return true if @options[:delivered_to_account_id]
local_usernames = (as_array(@object['to']) + as_array(@object['cc'])).uniq.select { |uri| ActivityPub::TagManager.instance.local_uri?(uri) }.map { |uri| ActivityPub::TagManager.instance.uri_to_local_id(uri, :username) } local_usernames = (as_array(audience_to) + as_array(audience_cc)).uniq.select { |uri| ActivityPub::TagManager.instance.local_uri?(uri) }.map { |uri| ActivityPub::TagManager.instance.uri_to_local_id(uri, :username) }
return false if local_usernames.empty? return false if local_usernames.empty?

View File

@ -14,10 +14,18 @@ module LdapAuthenticable
end end
def ldap_get_user(attributes = {}) def ldap_get_user(attributes = {})
resource = joins(:account).find_by(accounts: { username: attributes[Devise.ldap_uid.to_sym].first }) safe_username = attributes[Devise.ldap_uid.to_sym].first
if Devise.ldap_uid_conversion_enabled
keys = Regexp.union(Devise.ldap_uid_conversion_search.chars)
replacement = Devise.ldap_uid_conversion_replace
safe_username = safe_username.gsub(keys, replacement)
end
resource = joins(:account).find_by(accounts: { username: safe_username })
if resource.blank? if resource.blank?
resource = new(email: attributes[:mail].first, agreement: true, account_attributes: { username: attributes[Devise.ldap_uid.to_sym].first }, admin: false, external: true, confirmed_at: Time.now.utc) resource = new(email: attributes[:mail].first, agreement: true, account_attributes: { username: safe_username }, admin: false, external: true, confirmed_at: Time.now.utc)
resource.save! resource.save!
end end

View File

@ -19,6 +19,8 @@ class ReportFilter
def scope_for(key, value) def scope_for(key, value)
case key.to_sym case key.to_sym
when :by_target_domain
Report.where(target_account: Account.where(domain: value))
when :resolved when :resolved
Report.resolved Report.resolved
when :account_id when :account_id

View File

@ -8,6 +8,20 @@
%li= filter_link_to t('admin.reports.unresolved'), resolved: nil %li= filter_link_to t('admin.reports.unresolved'), resolved: nil
%li= filter_link_to t('admin.reports.resolved'), resolved: '1' %li= filter_link_to t('admin.reports.resolved'), resolved: '1'
= form_tag admin_reports_url, method: 'GET', class: 'simple_form' do
.fields-group
- Admin::FilterHelper::REPORT_FILTERS.each do |key|
- if params[key].present?
= hidden_field_tag key, params[key]
- %i(by_target_domain).each do |key|
.input.string.optional
= text_field_tag key, params[key], class: 'string optional', placeholder: I18n.t("admin.reports.#{key}")
.actions
%button= t('admin.accounts.search')
= link_to t('admin.accounts.reset'), admin_reports_path, class: 'button negative'
- @reports.group_by(&:target_account_id).each do |target_account_id, reports| - @reports.group_by(&:target_account_id).each do |target_account_id, reports|
- target_account = reports.first.target_account - target_account = reports.first.target_account
.report-card .report-card

View File

@ -5,8 +5,8 @@
.filter-subset .filter-subset
%strong= t 'relationships.relationship' %strong= t 'relationships.relationship'
%ul %ul
%li= filter_link_to t('accounts.following', count: current_account.following_count), relationship: nil %li= filter_link_to t('relationships.following'), relationship: nil
%li= filter_link_to t('accounts.followers', count: current_account.followers_count), relationship: 'followed_by' %li= filter_link_to t('relationships.followers'), relationship: 'followed_by'
%li= filter_link_to t('relationships.mutual'), relationship: 'mutual' %li= filter_link_to t('relationships.mutual'), relationship: 'mutual'
.filter-subset .filter-subset

View File

@ -61,6 +61,12 @@ module Devise
@@ldap_tls_no_verify = false @@ldap_tls_no_verify = false
mattr_accessor :ldap_search_filter mattr_accessor :ldap_search_filter
@@ldap_search_filter = nil @@ldap_search_filter = nil
mattr_accessor :ldap_uid_conversion_enabled
@@ldap_uid_conversion_enabled = false
mattr_accessor :ldap_uid_conversion_search
@@ldap_uid_conversion_search = nil
mattr_accessor :ldap_uid_conversion_replace
@@ldap_uid_conversion_replace = nil
class Strategies::PamAuthenticatable class Strategies::PamAuthenticatable
def valid? def valid?
@ -365,5 +371,8 @@ Devise.setup do |config|
config.ldap_uid = ENV.fetch('LDAP_UID', 'cn') config.ldap_uid = ENV.fetch('LDAP_UID', 'cn')
config.ldap_tls_no_verify = ENV['LDAP_TLS_NO_VERIFY'] == 'true' config.ldap_tls_no_verify = ENV['LDAP_TLS_NO_VERIFY'] == 'true'
config.ldap_search_filter = ENV.fetch('LDAP_SEARCH_FILTER', '%{uid}=%{email}') config.ldap_search_filter = ENV.fetch('LDAP_SEARCH_FILTER', '%{uid}=%{email}')
config.ldap_uid_conversion_enabled = ENV['LDAP_UID_CONVERSION_ENABLED'] == 'true'
config.ldap_uid_conversion_search = ENV.fetch('LDAP_UID_CONVERSION_SEARCH', '.,- ')
config.ldap_uid_conversion_replace = ENV.fetch('LDAP_UID_CONVERSION_REPLACE', '_')
end end
end end

View File

@ -8,8 +8,20 @@ Doorkeeper.configure do
end end
resource_owner_from_credentials do |_routes| resource_owner_from_credentials do |_routes|
if Devise.ldap_authentication
user = User.authenticate_with_ldap({ :email => request.params[:username], :password => request.params[:password] })
end
if Devise.pam_authentication
user ||= User.authenticate_with_ldap({ :email => request.params[:username], :password => request.params[:password] })
end
if user.nil?
user = User.find_by(email: request.params[:username]) user = User.find_by(email: request.params[:username])
user if !user&.otp_required_for_login? && user&.valid_password?(request.params[:password]) user = nil unless user.valid_password?(request.params[:password])
end
user if !user&.otp_required_for_login?
end end
# If you want to restrict access to the web interface for adding oauth authorized applications, you need to declare the block below. # If you want to restrict access to the web interface for adding oauth authorized applications, you need to declare the block below.

View File

@ -406,6 +406,7 @@ en:
are_you_sure: Are you sure? are_you_sure: Are you sure?
assign_to_self: Assign to me assign_to_self: Assign to me
assigned: Assigned moderator assigned: Assigned moderator
by_target_domain: Domain of reported account
comment: comment:
none: None none: None
created_at: Reported created_at: Reported
@ -938,6 +939,8 @@ en:
relationships: relationships:
activity: Account activity activity: Account activity
dormant: Dormant dormant: Dormant
followers: Followers
following: Following
last_active: Last active last_active: Last active
most_recent: Most recent most_recent: Most recent
moved: Moved moved: Moved