Merge remote-tracking branch 'tootsuite/master' into merge-upstream

Conflicts:
      app/javascript/styles/mastodon/components.scss
signup-info-prompt
David Yip 2018-02-02 08:39:52 -06:00
commit 4c1fd9a19c
No known key found for this signature in database
GPG Key ID: 7DA0036508FCC0CC
30 changed files with 244 additions and 95 deletions

View File

@ -31,6 +31,9 @@ gem 'iso-639'
gem 'cld3', '~> 3.2.0' gem 'cld3', '~> 3.2.0'
gem 'devise', '~> 4.4' gem 'devise', '~> 4.4'
gem 'devise-two-factor', '~> 3.0' gem 'devise-two-factor', '~> 3.0'
gem 'devise_pam_authenticatable2', '~> 8.0'
gem 'doorkeeper', '~> 4.2' gem 'doorkeeper', '~> 4.2'
gem 'fast_blank', '~> 1.0' gem 'fast_blank', '~> 1.0'
gem 'goldfinger', '~> 2.1' gem 'goldfinger', '~> 2.1'

View File

@ -137,6 +137,9 @@ GEM
devise (~> 4.0) devise (~> 4.0)
railties (< 5.2) railties (< 5.2)
rotp (~> 2.0) rotp (~> 2.0)
devise_pam_authenticatable2 (8.0.1)
devise (>= 4.0.0)
rpam2 (~> 3.0)
diff-lcs (1.3) diff-lcs (1.3)
docile (1.1.5) docile (1.1.5)
domain_name (0.5.20170404) domain_name (0.5.20170404)
@ -215,7 +218,7 @@ GEM
httplog (0.99.7) httplog (0.99.7)
colorize colorize
rack rack
i18n (0.9.1) i18n (0.9.3)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
i18n-tasks (0.9.19) i18n-tasks (0.9.19)
activesupport (>= 4.0.2) activesupport (>= 4.0.2)
@ -284,7 +287,7 @@ GEM
mimemagic (0.3.2) mimemagic (0.3.2)
mini_mime (1.0.0) mini_mime (1.0.0)
mini_portile2 (2.3.0) mini_portile2 (2.3.0)
minitest (5.10.3) minitest (5.11.3)
msgpack (1.1.0) msgpack (1.1.0)
multi_json (1.12.2) multi_json (1.12.2)
net-scp (1.2.1) net-scp (1.2.1)
@ -307,7 +310,7 @@ GEM
http (~> 3.0) http (~> 3.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
ox (2.8.2) ox (2.8.2)
paperclip (5.1.0) paperclip (5.2.1)
activemodel (>= 4.2.0) activemodel (>= 4.2.0)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
cocaine (~> 0.5.5) cocaine (~> 0.5.5)
@ -421,6 +424,7 @@ GEM
actionpack (>= 4.2.0, < 5.3) actionpack (>= 4.2.0, < 5.3)
railties (>= 4.2.0, < 5.3) railties (>= 4.2.0, < 5.3)
rotp (2.1.2) rotp (2.1.2)
rpam2 (3.1.0)
rqrcode (0.10.1) rqrcode (0.10.1)
chunky_png (~> 1.0) chunky_png (~> 1.0)
rspec-core (3.7.0) rspec-core (3.7.0)
@ -571,6 +575,7 @@ DEPENDENCIES
climate_control (~> 0.2) climate_control (~> 0.2)
devise (~> 4.4) devise (~> 4.4)
devise-two-factor (~> 3.0) devise-two-factor (~> 3.0)
devise_pam_authenticatable2 (~> 8.0)
doorkeeper (~> 4.2) doorkeeper (~> 4.2)
dotenv-rails (~> 2.2) dotenv-rails (~> 2.2)
fabrication (~> 2.18) fabrication (~> 2.18)

View File

@ -1,10 +1,12 @@
# frozen_string_literal: true # frozen_string_literal: true
class ActivityPub::OutboxesController < Api::BaseController class ActivityPub::OutboxesController < Api::BaseController
include SignatureVerification
before_action :set_account before_action :set_account
def show def show
@statuses = @account.statuses.permitted_for(@account, current_account).paginate_by_max_id(20, params[:max_id], params[:since_id]) @statuses = @account.statuses.permitted_for(@account, signed_request_account).paginate_by_max_id(20, params[:max_id], params[:since_id])
@statuses = cache_collection(@statuses, Status) @statuses = cache_collection(@statuses, Status)
render json: outbox_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json' render json: outbox_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'

View File

@ -15,6 +15,7 @@ class ApplicationController < ActionController::Base
helper_method :current_flavour helper_method :current_flavour
helper_method :current_skin helper_method :current_skin
helper_method :single_user_mode? helper_method :single_user_mode?
helper_method :use_pam?
rescue_from ActionController::RoutingError, with: :not_found rescue_from ActionController::RoutingError, with: :not_found
rescue_from ActiveRecord::RecordNotFound, with: :not_found rescue_from ActiveRecord::RecordNotFound, with: :not_found
@ -145,6 +146,10 @@ class ApplicationController < ActionController::Base
@single_user_mode ||= Rails.configuration.x.single_user_mode && Account.exists? @single_user_mode ||= Rails.configuration.x.single_user_mode && Account.exists?
end end
def use_pam?
Devise.pam_authentication
end
def current_account def current_account
@current_account ||= current_user.try(:account) @current_account ||= current_user.try(:account)
end end

View File

@ -15,6 +15,11 @@ class Auth::RegistrationsController < Devise::RegistrationsController
protected protected
def update_resource(resource, params)
params[:password] = nil if Devise.pam_authentication && resource.encrypted_password.blank?
super
end
def build_resource(hash = nil) def build_resource(hash = nil)
super(hash) super(hash)

View File

@ -29,7 +29,11 @@ class Auth::SessionsController < Devise::SessionsController
if session[:otp_user_id] if session[:otp_user_id]
User.find(session[:otp_user_id]) User.find(session[:otp_user_id])
elsif user_params[:email] elsif user_params[:email]
User.find_for_authentication(email: user_params[:email]) if use_pam? && Devise.check_at_sign && user_params[:email].index('@').nil?
User.joins(:account).find_by(accounts: { username: user_params[:email] })
else
User.find_for_authentication(email: user_params[:email])
end
end end
end end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
module SignatureAuthentication
extend ActiveSupport::Concern
include SignatureVerification
def current_account
super || signed_request_account
end
end

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class StatusesController < ApplicationController class StatusesController < ApplicationController
include SignatureAuthentication
include Authorization include Authorization
layout 'public' layout 'public'

View File

@ -10,6 +10,7 @@ class StreamEntriesController < ApplicationController
before_action :set_stream_entry before_action :set_stream_entry
before_action :set_link_headers before_action :set_link_headers
before_action :check_account_suspension before_action :check_account_suspension
before_action :set_cache_headers
def show def show
respond_to do |format| respond_to do |format|
@ -20,6 +21,10 @@ class StreamEntriesController < ApplicationController
end end
format.atom do format.atom do
unless @stream_entry.hidden?
skip_session!
expires_in 3.minutes, public: true
end
render xml: OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.entry(@stream_entry, true)) render xml: OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.entry(@stream_entry, true))
end end
end end

View File

@ -133,9 +133,7 @@ export default class ColumnHeader extends React.PureComponent {
<h1 className={buttonClassName}> <h1 className={buttonClassName}>
<button onClick={this.handleTitleClick}> <button onClick={this.handleTitleClick}>
<i className={`fa fa-fw fa-${icon} column-header__icon`} /> <i className={`fa fa-fw fa-${icon} column-header__icon`} />
<span className='column-header__title'> {title}
{title}
</span>
</button> </button>
<div className='column-header__buttons'> <div className='column-header__buttons'>

View File

@ -12,6 +12,7 @@ import Motion from '../ui/util/optional_motion';
import spring from 'react-motion/lib/spring'; import spring from 'react-motion/lib/spring';
import SearchResultsContainer from './containers/search_results_container'; import SearchResultsContainer from './containers/search_results_container';
import { changeComposing } from '../../actions/compose'; import { changeComposing } from '../../actions/compose';
import elephantUIPlane from '../../../images/elephant_ui_plane.svg';
const messages = defineMessages({ const messages = defineMessages({
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
@ -94,7 +95,11 @@ export default class Compose extends React.PureComponent {
<div className='drawer__inner' onFocus={this.onFocus}> <div className='drawer__inner' onFocus={this.onFocus}>
<NavigationContainer onClose={this.onBlur} /> <NavigationContainer onClose={this.onBlur} />
<ComposeFormContainer /> <ComposeFormContainer />
{multiColumn && <div className='mastodon' />} {multiColumn && (
<div className='drawer__inner__mastodon'>
<img alt='' src={elephantUIPlane} />
</div>
)}
</div> </div>
<Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}> <Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}>

View File

@ -1766,7 +1766,7 @@
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
background: lighten($ui-base-color, 13%) url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 234.80078 31.757813" width="234.80078" height="31.757812"><path d="M19.599609 0c-1.05 0-2.10039.375-2.90039 1.125L0 16.925781v14.832031h234.80078V17.025391l-16.5-15.900391c-1.6-1.5-4.20078-1.5-5.80078 0l-13.80078 13.099609c-1.6 1.5-4.19883 1.5-5.79883 0L179.09961 1.125c-1.6-1.5-4.19883-1.5-5.79883 0L159.5 14.224609c-1.6 1.5-4.20078 1.5-5.80078 0L139.90039 1.125c-1.6-1.5-4.20078-1.5-5.80078 0l-13.79883 13.099609c-1.6 1.5-4.20078 1.5-5.80078 0L100.69922 1.125c-1.600001-1.5-4.198829-1.5-5.798829 0l-13.59961 13.099609c-1.6 1.5-4.200781 1.5-5.800781 0L61.699219 1.125c-1.6-1.5-4.198828-1.5-5.798828 0L42.099609 14.224609c-1.6 1.5-4.198828 1.5-5.798828 0L22.5 1.125C21.7.375 20.649609 0 19.599609 0z" fill="#{hex-color($ui-base-color)}"/></svg>') no-repeat bottom / 100% auto; background: lighten($ui-base-color, 13%);
box-sizing: border-box; box-sizing: border-box;
padding: 0; padding: 0;
display: flex; display: flex;
@ -1779,10 +1779,19 @@
&.darker { &.darker {
background: $ui-base-color; background: $ui-base-color;
} }
}
> .mastodon { .drawer__inner__mastodon {
background: url('~images/elephant_ui_plane.svg') no-repeat left bottom / contain; background: lighten($ui-base-color, 13%) url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 234.80078 31.757813" width="234.80078" height="31.757812"><path d="M19.599609 0c-1.05 0-2.10039.375-2.90039 1.125L0 16.925781v14.832031h234.80078V17.025391l-16.5-15.900391c-1.6-1.5-4.20078-1.5-5.80078 0l-13.80078 13.099609c-1.6 1.5-4.19883 1.5-5.79883 0L179.09961 1.125c-1.6-1.5-4.19883-1.5-5.79883 0L159.5 14.224609c-1.6 1.5-4.20078 1.5-5.80078 0L139.90039 1.125c-1.6-1.5-4.20078-1.5-5.80078 0l-13.79883 13.099609c-1.6 1.5-4.20078 1.5-5.80078 0L100.69922 1.125c-1.600001-1.5-4.198829-1.5-5.798829 0l-13.59961 13.099609c-1.6 1.5-4.200781 1.5-5.800781 0L61.699219 1.125c-1.6-1.5-4.198828-1.5-5.798828 0L42.099609 14.224609c-1.6 1.5-4.198828 1.5-5.798828 0L22.5 1.125C21.7.375 20.649609 0 19.599609 0z" fill="#{hex-color($ui-base-color)}"/></svg>') no-repeat bottom / 100% auto;
flex: 1; flex: 1;
min-height: 47px;
> img {
display: block;
object-fit: contain;
object-position: bottom left;
width: 100%;
height: 100%;
} }
} }
@ -1913,7 +1922,7 @@
font-family: inherit; font-family: inherit;
color: $ui-highlight-color; color: $ui-highlight-color;
cursor: pointer; cursor: pointer;
flex: 0 0 auto; white-space: nowrap;
font-size: 16px; font-size: 16px;
padding: 0 5px 0 0; padding: 0 5px 0 0;
z-index: 3; z-index: 3;
@ -2403,15 +2412,17 @@
overflow: hidden; overflow: hidden;
& > button { & > button {
display: flex;
flex: auto;
margin: 0; margin: 0;
border: none; border: none;
padding: 15px; padding: 15px 0 15px 15px;
color: inherit; color: inherit;
background: transparent; background: transparent;
font: inherit; font: inherit;
text-align: left; text-align: left;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
flex: 1;
} }
&.active { &.active {
@ -2432,7 +2443,6 @@
.column-header__buttons { .column-header__buttons {
height: 48px; height: 48px;
display: flex; display: flex;
margin-left: 0;
} }
.column-header__links .text-btn { .column-header__links .text-btn {
@ -2512,14 +2522,6 @@
} }
} }
.column-header__title {
display: inline-block;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
flex: 1;
}
.text-btn { .text-btn {
display: inline-block; display: inline-block;
padding: 0; padding: 0;

View File

@ -15,7 +15,8 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
account: @account, account: @account,
reblog: original_status, reblog: original_status,
uri: @json['id'], uri: @json['id'],
created_at: @options[:override_timestamps] ? nil : @json['published'] created_at: @options[:override_timestamps] ? nil : @json['published'],
visibility: original_status.visibility
) )
distribute(status) distribute(status)
@ -35,6 +36,6 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
end end
def announceable?(status) def announceable?(status)
status.public_visibility? || status.unlisted_visibility? status.account_id == @account.id || status.public_visibility? || status.unlisted_visibility?
end end
end end

View File

@ -34,6 +34,7 @@
# disabled :boolean default(FALSE), not null # disabled :boolean default(FALSE), not null
# moderator :boolean default(FALSE), not null # moderator :boolean default(FALSE), not null
# invite_id :integer # invite_id :integer
# remember_token :string
# #
class User < ApplicationRecord class User < ApplicationRecord
@ -50,6 +51,8 @@ class User < ApplicationRecord
devise :registerable, :recoverable, :rememberable, :trackable, :validatable, devise :registerable, :recoverable, :rememberable, :trackable, :validatable,
:confirmable :confirmable
devise :pam_authenticatable
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
accepts_nested_attributes_for :account accepts_nested_attributes_for :account
@ -84,6 +87,33 @@ class User < ApplicationRecord
attr_accessor :invite_code attr_accessor :invite_code
def pam_conflict(_)
# block pam login tries on traditional account
nil
end
def pam_conflict?
return false unless Devise.pam_authentication
encrypted_password.present? && is_pam_account?
end
def pam_get_name
return account.username if account.present?
super
end
def pam_setup(_attributes)
acc = Account.new(username: pam_get_name)
acc.save!(validate: false)
self.email = "#{acc.username}@#{find_pam_suffix}" if email.nil? && find_pam_suffix
self.confirmed_at = Time.now.utc
self.admin = false
self.account = acc
acc.destroy! unless save
end
def confirmed? def confirmed?
confirmed_at.present? confirmed_at.present?
end end
@ -213,6 +243,45 @@ class User < ApplicationRecord
@invite_code = code @invite_code = code
end end
def password_required?
return false if Devise.pam_authentication
super
end
def send_reset_password_instructions
return false if encrypted_password.blank? && Devise.pam_authentication
super
end
def reset_password!(new_password, new_password_confirmation)
return false if encrypted_password.blank? && Devise.pam_authentication
super
end
def self.pam_get_user(attributes = {})
if attributes[:email]
resource =
if Devise.check_at_sign && !attributes[:email].index('@')
joins(:account).find_by(accounts: { username: attributes[:email] })
else
find_by(email: attributes[:email])
end
if resource.blank?
resource = new(email: attributes[:email])
if Devise.check_at_sign && !resource[:email].index('@')
resource[:email] = "#{attributes[:email]}@#{resource.find_pam_suffix}"
end
end
resource
end
end
def self.authenticate_with_pam(attributes = {})
return nil unless Devise.pam_authentication
super
end
protected protected
def send_devise_notification(notification, *args) def send_devise_notification(notification, *args)

View File

@ -91,13 +91,13 @@ class FetchLinkCardService < BaseService
case @card.type case @card.type
when 'link' when 'link'
@card.image = URI.parse(embed.thumbnail_url) if embed.respond_to?(:thumbnail_url) @card.image_remote_url = embed.thumbnail_url if embed.respond_to?(:thumbnail_url)
when 'photo' when 'photo'
return false unless embed.respond_to?(:url) return false unless embed.respond_to?(:url)
@card.embed_url = embed.url @card.embed_url = embed.url
@card.image = URI.parse(embed.url) @card.image_remote_url = embed.url
@card.width = embed.width.presence || 0 @card.width = embed.width.presence || 0
@card.height = embed.height.presence || 0 @card.height = embed.height.presence || 0
when 'video' when 'video'
@card.width = embed.width.presence || 0 @card.width = embed.width.presence || 0
@card.height = embed.height.presence || 0 @card.height = embed.height.presence || 0

View File

@ -8,7 +8,13 @@ class UnreservedUsernameValidator < ActiveModel::Validator
private private
def pam_controlled?(value)
return false unless Devise.pam_authentication && Devise.pam_controlled_service
Rpam2.account(Devise.pam_controlled_service, value).present?
end
def reserved_username?(value) def reserved_username?(value)
return true if pam_controlled?(value)
return false unless Setting.reserved_usernames return false unless Setting.reserved_usernames
Setting.reserved_usernames.include?(value.downcase) Setting.reserved_usernames.include?(value.downcase)
end end

View File

@ -0,0 +1,16 @@
.container.links
.brand
= link_to root_url do
= image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
%ul.nav
%li
- if user_signed_in?
= link_to t('settings.back'), root_url, class: 'webapp-btn'
- else
= link_to t('auth.login'), new_user_session_path, class: 'webapp-btn'
%li= link_to t('about.about_this'), about_more_path
%li
= link_to 'https://joinmastodon.org/' do
= "#{t('about.other_instances')}"
%i.fa.fa-external-link{ style: 'padding-left: 5px;' }

View File

@ -7,22 +7,7 @@
.landing-page .landing-page
.header-wrapper.compact .header-wrapper.compact
.header .header
.container.links = render 'links'
.brand
= link_to root_url do
= image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
%ul.nav
%li
- if user_signed_in?
= link_to t('settings.back'), root_url, class: 'webapp-btn'
- else
= link_to t('auth.login'), new_user_session_path, class: 'webapp-btn'
%li= link_to t('about.about_this'), about_more_path
%li
= link_to 'https://joinmastodon.org/' do
= "#{t('about.other_instances')}"
%i.fa.fa-external-link{ style: 'padding-left: 5px;' }
.container.hero .container.hero
.heading .heading

View File

@ -11,22 +11,7 @@
= image_tag asset_pack_path('elephant-fren.png'), alt: '', role: 'presentation', class: 'mascot' = image_tag asset_pack_path('elephant-fren.png'), alt: '', role: 'presentation', class: 'mascot'
.header .header
.container.links = render 'links'
.brand
= link_to root_url do
= image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
%ul.nav
%li
- if user_signed_in?
= link_to t('settings.back'), root_url, class: 'webapp-btn'
- else
= link_to t('auth.login'), new_user_session_path, class: 'webapp-btn'
%li= link_to t('about.about_this'), about_more_path
%li
= link_to 'https://joinmastodon.org/' do
= "#{t('about.other_instances')}"
%i.fa.fa-external-link{ style: 'padding-left: 5px;' }
.container.hero .container.hero
.floats .floats

View File

@ -4,19 +4,7 @@
.landing-page .landing-page
.header-wrapper.compact .header-wrapper.compact
.header .header
.container.links = render 'links'
.brand
= link_to root_url do
= image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
%ul.nav
%li
- if user_signed_in?
= link_to t('settings.back'), root_url, class: 'webapp-btn'
- else
= link_to t('auth.login'), new_user_session_path, class: 'webapp-btn'
%li= link_to t('about.about_this'), about_more_path
%li= link_to t('about.other_instances'), 'https://joinmastodon.org/'
.extended-description .extended-description
.container .container

View File

@ -1,14 +1,18 @@
- content_for :page_title do - content_for :page_title do
= t('auth.set_new_password') = t('auth.set_new_password')
= simple_form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| = simple_form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f|
= render 'shared/error_messages', object: resource = render 'shared/error_messages', object: resource
= f.input :reset_password_token, as: :hidden
= f.input :password, autofocus: true, placeholder: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off' } - if use_pam? || current_user.encrypted_password.present?
= f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'off' } = f.input :reset_password_token, as: :hidden
.actions = f.input :password, autofocus: true, placeholder: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off' }
= f.button :button, t('auth.set_new_password'), type: :submit = f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'off' }
.actions
= f.button :button, t('auth.set_new_password'), type: :submit
- else
= t('simple_form.labels.defaults.pam_account')
.form-footer= render 'auth/shared/links' .form-footer= render 'auth/shared/links'

View File

@ -4,13 +4,16 @@
= simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put, class: 'auth_edit' }) do |f| = simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put, class: 'auth_edit' }) do |f|
= render 'shared/error_messages', object: resource = render 'shared/error_messages', object: resource
= f.input :email, placeholder: t('simple_form.labels.defaults.email'), input_html: { 'aria-label' => t('simple_form.labels.defaults.email') } - if !use_pam? || current_user.encrypted_password.present?
= f.input :password, placeholder: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off' } = f.input :email, placeholder: t('simple_form.labels.defaults.email'), input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
= f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'off' } = f.input :password, placeholder: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off' }
= f.input :current_password, placeholder: t('simple_form.labels.defaults.current_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.current_password'), :autocomplete => 'off' } = f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'off' }
= f.input :current_password, placeholder: t('simple_form.labels.defaults.current_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.current_password'), :autocomplete => 'off' }
.actions .actions
= f.button :button, t('generic.save_changes'), type: :submit = f.button :button, t('generic.save_changes'), type: :submit
- else
= t('simple_form.labels.defaults.pam_account')
%hr/ %hr/

View File

@ -5,7 +5,10 @@
= render partial: 'shared/og' = render partial: 'shared/og'
= simple_form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| = simple_form_for(resource, as: resource_name, url: session_path(resource_name)) do |f|
= f.input :email, autofocus: true, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') } - if use_pam?
= f.input :email, autofocus: true, placeholder: t('simple_form.labels.defaults.username_or_email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username_or_email') }
- else
= f.input :email, autofocus: true, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
= f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off' } = f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off' }
.actions .actions

View File

@ -4,6 +4,9 @@
= simple_form_for current_user, url: settings_preferences_path, html: { method: :put } do |f| = simple_form_for current_user, url: settings_preferences_path, html: { method: :put } do |f|
= render 'shared/error_messages', object: current_user = render 'shared/error_messages', object: current_user
.actions
= f.button :button, t('generic.save_changes'), type: :submit
%h4= t 'preferences.languages' %h4= t 'preferences.languages'
.fields-group .fields-group

View File

@ -30,6 +30,19 @@ Warden::Manager.before_logout do |_, warden|
warden.cookies.delete('_session_id') warden.cookies.delete('_session_id')
end end
module Devise
mattr_accessor :pam_authentication
@@pam_authentication = false
mattr_accessor :pam_controlled_service
@@pam_controlled_service = nil
class Strategies::PamAuthenticatable
def valid?
super && ::Devise.pam_authentication
end
end
end
Devise.setup do |config| Devise.setup do |config|
config.warden do |manager| config.warden do |manager|
manager.default_strategies(scope: :user).unshift :two_factor_authenticatable manager.default_strategies(scope: :user).unshift :two_factor_authenticatable
@ -96,7 +109,7 @@ Devise.setup do |config|
# given strategies, for example, `config.http_authenticatable = [:database]` will # given strategies, for example, `config.http_authenticatable = [:database]` will
# enable it only for database authentication. The supported strategies are: # enable it only for database authentication. The supported strategies are:
# :database = Support basic authentication with authentication key + password # :database = Support basic authentication with authentication key + password
config.http_authenticatable = [:database] config.http_authenticatable = [:pam, :database]
# If 401 status code should be returned for AJAX requests. True by default. # If 401 status code should be returned for AJAX requests. True by default.
# config.http_authenticatable_on_xhr = true # config.http_authenticatable_on_xhr = true
@ -301,4 +314,23 @@ Devise.setup do |config|
# When using OmniAuth, Devise cannot automatically set OmniAuth path, # When using OmniAuth, Devise cannot automatically set OmniAuth path,
# so you need to do it manually. For the users scope, it would be: # so you need to do it manually. For the users scope, it would be:
# config.omniauth_path_prefix = '/my_engine/users/auth' # config.omniauth_path_prefix = '/my_engine/users/auth'
# PAM: only look for email field
config.usernamefield = nil
config.emailfield = "email"
# authentication with pam possible
# if not enabled, all pam settings are ignored
#config.pam_authentication = true
# check if email is actually a username
config.check_at_sign = true
# suffix for email address generation (warning: without pam must provide email in the pam environment)
config.pam_default_suffix = "pam"
# name of the pam service
# pam "auth" section is evaluated
config.pam_default_service = "rpam"
# name of the pam service used for checking if an user can register
# pam "account" section is evaluated
# nil for allowing registration of pam names (not recommended)
config.pam_controlled_service = "rpam"
end end

View File

@ -53,6 +53,7 @@ de:
severity: Gewichtung severity: Gewichtung
type: Importtyp type: Importtyp
username: Profilname username: Profilname
username_or_email: Profilname oder Email
interactions: interactions:
must_be_follower: Benachrichtigungen von Nicht-Folgenden blockieren must_be_follower: Benachrichtigungen von Nicht-Folgenden blockieren
must_be_following: Benachrichtigungen von Profilen blockieren, denen ich nicht folge must_be_following: Benachrichtigungen von Profilen blockieren, denen ich nicht folge

View File

@ -54,6 +54,7 @@ en:
severity: Severity severity: Severity
type: Import type type: Import type
username: Username username: Username
username_or_email: Username or Email
interactions: interactions:
must_be_follower: Block notifications from non-followers must_be_follower: Block notifications from non-followers
must_be_following: Block notifications from people you don't follow must_be_following: Block notifications from people you don't follow

View File

@ -0,0 +1,5 @@
class AddRememberTokenToUsers < ActiveRecord::Migration[5.1]
def change
add_column :users, :remember_token, :string, null: true
end
end

View File

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20180106000232) do ActiveRecord::Schema.define(version: 20180109143959) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -496,6 +496,7 @@ ActiveRecord::Schema.define(version: 20180106000232) do
t.boolean "disabled", default: false, null: false t.boolean "disabled", default: false, null: false
t.boolean "moderator", default: false, null: false t.boolean "moderator", default: false, null: false
t.bigint "invite_id" t.bigint "invite_id"
t.string "remember_token"
t.index ["account_id"], name: "index_users_on_account_id" t.index ["account_id"], name: "index_users_on_account_id"
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
t.index ["email"], name: "index_users_on_email", unique: true t.index ["email"], name: "index_users_on_email", unique: true

View File

@ -52,7 +52,7 @@ RSpec.describe Setting, type: :model do
allow(RailsSettings::Settings).to receive(:object).with(key).and_return(object) allow(RailsSettings::Settings).to receive(:object).with(key).and_return(object)
allow(described_class).to receive(:default_settings).and_return(default_settings) allow(described_class).to receive(:default_settings).and_return(default_settings)
allow_any_instance_of(Settings::ScopedSettings).to receive(:thing_scoped).and_return(records) allow_any_instance_of(Settings::ScopedSettings).to receive(:thing_scoped).and_return(records)
Rails.cache.clear(cache_key) Rails.cache.delete(cache_key)
end end
let(:object) { nil } let(:object) { nil }