commit
7d59e25fbd
|
@ -183,6 +183,9 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
|
|||
# LDAP_BIND_DN=
|
||||
# LDAP_PASSWORD=
|
||||
# LDAP_UID=cn
|
||||
# LDAP_UID_CONVERSION_ENABLED=true
|
||||
# LDAP_UID_CONVERSION_SEARCH=., -
|
||||
# LDAP_UID_CONVERSION_REPLACE=_
|
||||
|
||||
# PAM authentication (optional)
|
||||
# PAM authentication uses for the email generation the "email" pam variable
|
||||
|
|
|
@ -204,6 +204,9 @@ STREAMING_CLUSTER_NUM=1
|
|||
# LDAP_PASSWORD=
|
||||
# LDAP_UID=cn
|
||||
# LDAP_SEARCH_FILTER=%{uid}=%{email}
|
||||
# LDAP_UID_CONVERSION_ENABLED=true
|
||||
# LDAP_UID_CONVERSION_SEARCH=., -
|
||||
# LDAP_UID_CONVERSION_REPLACE=_
|
||||
|
||||
# PAM authentication (optional)
|
||||
# PAM authentication uses for the email generation the "email" pam variable
|
||||
|
|
|
@ -55,7 +55,8 @@ module Admin
|
|||
params.permit(
|
||||
:account_id,
|
||||
:resolved,
|
||||
:target_account_id
|
||||
:target_account_id,
|
||||
:by_target_domain
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
module Admin::FilterHelper
|
||||
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
|
||||
CUSTOM_EMOJI_FILTERS = %i(local remote by_domain shortcode).freeze
|
||||
TAGS_FILTERS = %i(directory reviewed unreviewed pending_review popular active name).freeze
|
||||
|
|
|
@ -62,15 +62,22 @@ export default class ModalRoot extends React.PureComponent {
|
|||
} else if (!nextProps.children) {
|
||||
this.setState({ revealed: false });
|
||||
}
|
||||
if (!nextProps.children && !!this.props.children) {
|
||||
this.activeElement.focus({ preventScroll: true });
|
||||
this.activeElement = null;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
if (!this.props.children && !!prevProps.children) {
|
||||
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();
|
||||
}
|
||||
if (this.props.children) {
|
||||
|
|
|
@ -12,6 +12,7 @@ import IconButton from 'flavours/glitch/components/icon_button';
|
|||
import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp';
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
import { autoPlayGif } from 'flavours/glitch/util/initial_state';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const messages = defineMessages({
|
||||
more: { id: 'status.more', defaultMessage: 'More' },
|
||||
|
@ -193,7 +194,7 @@ class Conversation extends ImmutablePureComponent {
|
|||
|
||||
return (
|
||||
<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'>
|
||||
<AvatarComposite accounts={accounts} size={48} />
|
||||
</div>
|
||||
|
@ -201,7 +202,7 @@ class Conversation extends ImmutablePureComponent {
|
|||
<div className='conversation__content'>
|
||||
<div className='conversation__content__info'>
|
||||
<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 className='conversation__content__names' ref={this.setNamesRef}>
|
||||
|
|
|
@ -1507,6 +1507,16 @@
|
|||
flex: 0 0 auto;
|
||||
padding: 10px;
|
||||
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 {
|
||||
|
@ -1554,6 +1564,22 @@
|
|||
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 {
|
||||
|
|
|
@ -56,15 +56,21 @@ export default class ModalRoot extends React.PureComponent {
|
|||
} else if (!nextProps.children) {
|
||||
this.setState({ revealed: false });
|
||||
}
|
||||
if (!nextProps.children && !!this.props.children) {
|
||||
this.activeElement.focus();
|
||||
this.activeElement = null;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
if (!this.props.children && !!prevProps.children) {
|
||||
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) {
|
||||
requestAnimationFrame(() => {
|
||||
|
|
|
@ -12,6 +12,7 @@ import IconButton from 'mastodon/components/icon_button';
|
|||
import RelativeTimestamp from 'mastodon/components/relative_timestamp';
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
import { autoPlayGif } from 'mastodon/initial_state';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const messages = defineMessages({
|
||||
more: { id: 'status.more', defaultMessage: 'More' },
|
||||
|
@ -158,7 +159,7 @@ class Conversation extends ImmutablePureComponent {
|
|||
|
||||
return (
|
||||
<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'>
|
||||
<AvatarComposite accounts={accounts} size={48} />
|
||||
</div>
|
||||
|
@ -166,7 +167,7 @@ class Conversation extends ImmutablePureComponent {
|
|||
<div className='conversation__content'>
|
||||
<div className='conversation__content__info'>
|
||||
<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 className='conversation__content__names' ref={this.setNamesRef}>
|
||||
|
|
|
@ -6517,6 +6517,16 @@ noscript {
|
|||
flex: 0 0 auto;
|
||||
padding: 10px;
|
||||
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 {
|
||||
|
@ -6564,4 +6574,20 @@ noscript {
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,14 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||
|
||||
private
|
||||
|
||||
def audience_to
|
||||
@object['to'] || @json['to']
|
||||
end
|
||||
|
||||
def audience_cc
|
||||
@object['cc'] || @json['cc']
|
||||
end
|
||||
|
||||
def process_status
|
||||
@tags = []
|
||||
@mentions = []
|
||||
|
@ -75,7 +83,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||
end
|
||||
|
||||
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]
|
||||
|
||||
# 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
|
||||
|
||||
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
|
||||
elsif equals_or_includes?(@object['cc'], ActivityPub::TagManager::COLLECTIONS[:public])
|
||||
elsif equals_or_includes?(audience_cc, ActivityPub::TagManager::COLLECTIONS[:public])
|
||||
:unlisted
|
||||
elsif equals_or_includes?(@object['to'], @account.followers_url)
|
||||
elsif equals_or_includes?(audience_to, @account.followers_url)
|
||||
:private
|
||||
else
|
||||
:direct
|
||||
|
@ -304,7 +312,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||
|
||||
def audience_includes?(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
|
||||
|
||||
def replied_to_status
|
||||
|
@ -415,7 +423,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||
def addresses_local_accounts?
|
||||
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?
|
||||
|
||||
|
|
|
@ -14,10 +14,18 @@ module LdapAuthenticable
|
|||
end
|
||||
|
||||
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?
|
||||
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!
|
||||
end
|
||||
|
||||
|
|
|
@ -19,6 +19,8 @@ class ReportFilter
|
|||
|
||||
def scope_for(key, value)
|
||||
case key.to_sym
|
||||
when :by_target_domain
|
||||
Report.where(target_account: Account.where(domain: value))
|
||||
when :resolved
|
||||
Report.resolved
|
||||
when :account_id
|
||||
|
|
|
@ -8,6 +8,20 @@
|
|||
%li= filter_link_to t('admin.reports.unresolved'), resolved: nil
|
||||
%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|
|
||||
- target_account = reports.first.target_account
|
||||
.report-card
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
.filter-subset
|
||||
%strong= t 'relationships.relationship'
|
||||
%ul
|
||||
%li= filter_link_to t('accounts.following', count: current_account.following_count), relationship: nil
|
||||
%li= filter_link_to t('accounts.followers', count: current_account.followers_count), relationship: 'followed_by'
|
||||
%li= filter_link_to t('relationships.following'), relationship: nil
|
||||
%li= filter_link_to t('relationships.followers'), relationship: 'followed_by'
|
||||
%li= filter_link_to t('relationships.mutual'), relationship: 'mutual'
|
||||
|
||||
.filter-subset
|
||||
|
|
|
@ -61,6 +61,12 @@ module Devise
|
|||
@@ldap_tls_no_verify = false
|
||||
mattr_accessor :ldap_search_filter
|
||||
@@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
|
||||
def valid?
|
||||
|
@ -365,5 +371,8 @@ Devise.setup do |config|
|
|||
config.ldap_uid = ENV.fetch('LDAP_UID', 'cn')
|
||||
config.ldap_tls_no_verify = ENV['LDAP_TLS_NO_VERIFY'] == 'true'
|
||||
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
|
||||
|
|
|
@ -8,8 +8,20 @@ Doorkeeper.configure do
|
|||
end
|
||||
|
||||
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 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
|
||||
|
||||
# If you want to restrict access to the web interface for adding oauth authorized applications, you need to declare the block below.
|
||||
|
|
|
@ -406,6 +406,7 @@ en:
|
|||
are_you_sure: Are you sure?
|
||||
assign_to_self: Assign to me
|
||||
assigned: Assigned moderator
|
||||
by_target_domain: Domain of reported account
|
||||
comment:
|
||||
none: None
|
||||
created_at: Reported
|
||||
|
@ -938,6 +939,8 @@ en:
|
|||
relationships:
|
||||
activity: Account activity
|
||||
dormant: Dormant
|
||||
followers: Followers
|
||||
following: Following
|
||||
last_active: Last active
|
||||
most_recent: Most recent
|
||||
moved: Moved
|
||||
|
|
Loading…
Reference in New Issue