diff options
author | David Yip <yipdw@member.fsf.org> | 2018-02-04 16:36:19 -0600 |
---|---|---|
committer | David Yip <yipdw@member.fsf.org> | 2018-02-04 16:36:19 -0600 |
commit | a6fb1c58eead1357a307813c69f798f32bcf7cae (patch) | |
tree | d56d2721705d86a925279a737943cbaa28a60979 /app | |
parent | 530fcc1c14d2b7a38c1b734c0b18955f109f0f20 (diff) | |
parent | 38e0133e1b01c21a710111097102a6eb205b9b9b (diff) |
Merge remote-tracking branch 'origin/master' into merge-upstream
Conflicts: .env.production.sample app/controllers/auth/confirmations_controller.rb db/schema.rb
Diffstat (limited to 'app')
-rw-r--r-- | app/controllers/about_controller.rb | 2 | ||||
-rw-r--r-- | app/controllers/admin/settings_controller.rb | 2 | ||||
-rw-r--r-- | app/controllers/api/v1/accounts/relationships_controller.rb | 2 | ||||
-rw-r--r-- | app/controllers/auth/confirmations_controller.rb | 23 | ||||
-rw-r--r-- | app/controllers/auth/omniauth_callbacks_controller.rb | 33 | ||||
-rw-r--r-- | app/javascript/mastodon/containers/timeline_container.js | 12 | ||||
-rw-r--r-- | app/javascript/mastodon/features/standalone/community_timeline/index.js | 74 | ||||
-rw-r--r-- | app/javascript/mastodon/locales/defaultMessages.json | 9 | ||||
-rw-r--r-- | app/javascript/styles/mastodon/forms.scss | 18 | ||||
-rw-r--r-- | app/mailers/notification_mailer.rb | 6 | ||||
-rw-r--r-- | app/models/concerns/omniauthable.rb | 81 | ||||
-rw-r--r-- | app/models/form/admin_settings.rb | 2 | ||||
-rw-r--r-- | app/models/identity.rb | 22 | ||||
-rw-r--r-- | app/models/user.rb | 4 | ||||
-rw-r--r-- | app/validators/status_length_validator.rb | 2 | ||||
-rw-r--r-- | app/views/admin/settings/edit.html.haml | 3 | ||||
-rw-r--r-- | app/views/auth/confirmations/finish_signup.html.haml | 14 | ||||
-rw-r--r-- | app/views/auth/sessions/new.html.haml | 9 |
18 files changed, 310 insertions, 8 deletions
diff --git a/app/controllers/about_controller.rb b/app/controllers/about_controller.rb index 8785df14e..7b46b2228 100644 --- a/app/controllers/about_controller.rb +++ b/app/controllers/about_controller.rb @@ -36,7 +36,7 @@ class AboutController < ApplicationController def initial_state_params { - settings: {}, + settings: { known_fediverse: Setting.show_known_fediverse_at_about_page }, token: current_session&.token, } end diff --git a/app/controllers/admin/settings_controller.rb b/app/controllers/admin/settings_controller.rb index 487282dc3..a6214dc3f 100644 --- a/app/controllers/admin/settings_controller.rb +++ b/app/controllers/admin/settings_controller.rb @@ -19,6 +19,7 @@ module Admin min_invite_role activity_api_enabled peers_api_enabled + show_known_fediverse_at_about_page ).freeze BOOLEAN_SETTINGS = %w( @@ -28,6 +29,7 @@ module Admin show_staff_badge activity_api_enabled peers_api_enabled + show_known_fediverse_at_about_page ).freeze UPLOAD_SETTINGS = %w( diff --git a/app/controllers/api/v1/accounts/relationships_controller.rb b/app/controllers/api/v1/accounts/relationships_controller.rb index 91a942d75..6cc3da498 100644 --- a/app/controllers/api/v1/accounts/relationships_controller.rb +++ b/app/controllers/api/v1/accounts/relationships_controller.rb @@ -10,7 +10,7 @@ class Api::V1::Accounts::RelationshipsController < Api::BaseController accounts = Account.where(id: account_ids).select('id') # .where doesn't guarantee that our results are in the same order # we requested them, so return the "right" order to the requestor. - @accounts = accounts.index_by(&:id).values_at(*account_ids) + @accounts = accounts.index_by(&:id).values_at(*account_ids).compact render json: @accounts, each_serializer: REST::RelationshipSerializer, relationships: relationships end diff --git a/app/controllers/auth/confirmations_controller.rb b/app/controllers/auth/confirmations_controller.rb index 72b8e9dd8..f3e0ae257 100644 --- a/app/controllers/auth/confirmations_controller.rb +++ b/app/controllers/auth/confirmations_controller.rb @@ -3,6 +3,7 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController layout 'auth' + before_action :set_user, only: [:finish_signup] before_action :set_pack private @@ -10,4 +11,26 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController def set_pack use_pack 'auth' end + + # GET/PATCH /users/:id/finish_signup + def finish_signup + return unless request.patch? && params[:user] + if @user.update(user_params) + @user.skip_reconfirmation! + sign_in(@user, bypass: true) + redirect_to root_path, notice: I18n.t('devise.confirmations.send_instructions') + else + @show_errors = true + end + end + + private + + def set_user + @user = current_user + end + + def user_params + params.require(:user).permit(:email) + end end diff --git a/app/controllers/auth/omniauth_callbacks_controller.rb b/app/controllers/auth/omniauth_callbacks_controller.rb new file mode 100644 index 000000000..bbf63bed3 --- /dev/null +++ b/app/controllers/auth/omniauth_callbacks_controller.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController + skip_before_action :verify_authenticity_token + + def self.provides_callback_for(provider) + provider_id = provider.to_s.chomp '_oauth2' + + define_method provider do + @user = User.find_for_oauth(request.env['omniauth.auth'], current_user) + + if @user.persisted? + sign_in_and_redirect @user, event: :authentication + set_flash_message(:notice, :success, kind: provider_id.capitalize) if is_navigational_format? + else + session["devise.#{provider}_data"] = request.env['omniauth.auth'] + redirect_to new_user_registration_url + end + end + end + + Devise.omniauth_configs.each_key do |provider| + provides_callback_for provider + end + + def after_sign_in_path_for(resource) + if resource.email_verified? + root_path + else + finish_signup_path + end + end +end diff --git a/app/javascript/mastodon/containers/timeline_container.js b/app/javascript/mastodon/containers/timeline_container.js index e84c921ee..8719bb5c9 100644 --- a/app/javascript/mastodon/containers/timeline_container.js +++ b/app/javascript/mastodon/containers/timeline_container.js @@ -6,6 +6,7 @@ import { hydrateStore } from '../actions/store'; import { IntlProvider, addLocaleData } from 'react-intl'; import { getLocale } from '../locales'; import PublicTimeline from '../features/standalone/public_timeline'; +import CommunityTimeline from '../features/standalone/community_timeline'; import HashtagTimeline from '../features/standalone/hashtag_timeline'; import initialState from '../initial_state'; @@ -23,17 +24,24 @@ export default class TimelineContainer extends React.PureComponent { static propTypes = { locale: PropTypes.string.isRequired, hashtag: PropTypes.string, + showPublicTimeline: PropTypes.bool.isRequired, + }; + + static defaultProps = { + showPublicTimeline: initialState.settings.known_fediverse, }; render () { - const { locale, hashtag } = this.props; + const { locale, hashtag, showPublicTimeline } = this.props; let timeline; if (hashtag) { timeline = <HashtagTimeline hashtag={hashtag} />; - } else { + } else if (showPublicTimeline) { timeline = <PublicTimeline />; + } else { + timeline = <CommunityTimeline />; } return ( diff --git a/app/javascript/mastodon/features/standalone/community_timeline/index.js b/app/javascript/mastodon/features/standalone/community_timeline/index.js new file mode 100644 index 000000000..51e50e1f5 --- /dev/null +++ b/app/javascript/mastodon/features/standalone/community_timeline/index.js @@ -0,0 +1,74 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import StatusListContainer from '../../ui/containers/status_list_container'; +import { + refreshCommunityTimeline, + expandCommunityTimeline, +} from '../../../actions/timelines'; +import Column from '../../../components/column'; +import ColumnHeader from '../../../components/column_header'; +import { defineMessages, injectIntl } from 'react-intl'; +import { connectCommunityStream } from '../../../actions/streaming'; + +const messages = defineMessages({ + title: { id: 'standalone.public_title', defaultMessage: 'A look inside...' }, +}); + +@connect() +@injectIntl +export default class CommunityTimeline extends React.PureComponent { + + static propTypes = { + dispatch: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + }; + + handleHeaderClick = () => { + this.column.scrollTop(); + } + + setRef = c => { + this.column = c; + } + + componentDidMount () { + const { dispatch } = this.props; + + dispatch(refreshCommunityTimeline()); + this.disconnect = dispatch(connectCommunityStream()); + } + + componentWillUnmount () { + if (this.disconnect) { + this.disconnect(); + this.disconnect = null; + } + } + + handleLoadMore = () => { + this.props.dispatch(expandCommunityTimeline()); + } + + render () { + const { intl } = this.props; + + return ( + <Column ref={this.setRef}> + <ColumnHeader + icon='users' + title={intl.formatMessage(messages.title)} + onClick={this.handleHeaderClick} + /> + + <StatusListContainer + timelineId='community' + loadMore={this.handleLoadMore} + scrollKey='standalone_public_timeline' + trackScroll={false} + /> + </Column> + ); + } + +} diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index a15da83ab..a27f17b42 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -1237,6 +1237,15 @@ "id": "standalone.public_title" } ], + "path": "app/javascript/mastodon/features/standalone/community_timeline/index.json" + }, + { + "descriptors": [ + { + "defaultMessage": "A look inside...", + "id": "standalone.public_title" + } + ], "path": "app/javascript/mastodon/features/standalone/public_timeline/index.json" }, { diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss index 2bef53cff..dec7d2284 100644 --- a/app/javascript/styles/mastodon/forms.scss +++ b/app/javascript/styles/mastodon/forms.scss @@ -568,3 +568,21 @@ code { margin-bottom: 4px; } } + +.alternative-login { + margin-top: 20px; + margin-bottom: 20px; + + h4 { + font-size: 16px; + color: $ui-base-lighter-color; + text-align: center; + margin-bottom: 20px; + border: 0; + padding: 0; + } + + .button { + display: block; + } +} diff --git a/app/mailers/notification_mailer.rb b/app/mailers/notification_mailer.rb index 9fed4a636..b45844296 100644 --- a/app/mailers/notification_mailer.rb +++ b/app/mailers/notification_mailer.rb @@ -9,7 +9,7 @@ class NotificationMailer < ApplicationMailer @me = recipient @status = notification.target_status - return if @me.user.disabled? + return if @me.user.disabled? || @status.nil? locale_for_account(@me) do thread_by_conversation(@status.conversation) @@ -33,7 +33,7 @@ class NotificationMailer < ApplicationMailer @account = notification.from_account @status = notification.target_status - return if @me.user.disabled? + return if @me.user.disabled? || @status.nil? locale_for_account(@me) do thread_by_conversation(@status.conversation) @@ -46,7 +46,7 @@ class NotificationMailer < ApplicationMailer @account = notification.from_account @status = notification.target_status - return if @me.user.disabled? + return if @me.user.disabled? || @status.nil? locale_for_account(@me) do thread_by_conversation(@status.conversation) diff --git a/app/models/concerns/omniauthable.rb b/app/models/concerns/omniauthable.rb new file mode 100644 index 000000000..a3d55108d --- /dev/null +++ b/app/models/concerns/omniauthable.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +module Omniauthable + extend ActiveSupport::Concern + + TEMP_EMAIL_PREFIX = 'change@me' + TEMP_EMAIL_REGEX = /\Achange@me/ + + included do + def omniauth_providers + Devise.omniauth_configs.keys + end + + def email_verified? + email && email !~ TEMP_EMAIL_REGEX + end + end + + class_methods do + def find_for_oauth(auth, signed_in_resource = nil) + # EOLE-SSO Patch + auth.uid = (auth.uid[0][:uid] || auth.uid[0][:user]) if auth.uid.is_a? Hashie::Array + identity = Identity.find_for_oauth(auth) + + # If a signed_in_resource is provided it always overrides the existing user + # to prevent the identity being locked with accidentally created accounts. + # Note that this may leave zombie accounts (with no associated identity) which + # can be cleaned up at a later date. + user = signed_in_resource ? signed_in_resource : identity.user + user = create_for_oauth(auth) if user.nil? + + if identity.user.nil? + identity.user = user + identity.save! + end + + user + end + + def create_for_oauth(auth) + # Check if the user exists with provided email if the provider gives us a + # verified email. If no verified email was provided or the user already + # exists, we assign a temporary email and ask the user to verify it on + # the next step via Auth::ConfirmationsController.finish_signup + + user = User.new(user_params_from_auth(auth)) + user.account.avatar_remote_url = auth.info.image if auth.info.image =~ /\A#{URI.regexp(%w(http https))}\z/ + user.skip_confirmation! + user.save! + user + end + + private + + def user_params_from_auth(auth) + email_is_verified = auth.info.email && (auth.info.verified || auth.info.verified_email) + email = auth.info.email if email_is_verified && !User.exists?(email: auth.info.email) + + { + email: email ? email : "#{TEMP_EMAIL_PREFIX}-#{auth.uid}-#{auth.provider}.com", + password: Devise.friendly_token[0, 20], + account_attributes: { + username: ensure_unique_username(auth.uid), + display_name: [auth.info.first_name, auth.info.last_name].join(' '), + }, + } + end + + def ensure_unique_username(starting_username) + username = starting_username + i = 0 + + while Account.exists?(username: username) + i += 1 + username = "#{starting_username}_#{i}" + end + + username + end + end +end diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb index dd629279c..32922e7f1 100644 --- a/app/models/form/admin_settings.rb +++ b/app/models/form/admin_settings.rb @@ -34,6 +34,8 @@ class Form::AdminSettings :activity_api_enabled=, :peers_api_enabled, :peers_api_enabled=, + :show_known_fediverse_at_about_page, + :show_known_fediverse_at_about_page=, to: Setting ) end diff --git a/app/models/identity.rb b/app/models/identity.rb new file mode 100644 index 000000000..a5e0c09ec --- /dev/null +++ b/app/models/identity.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true +# == Schema Information +# +# Table name: identities +# +# id :integer not null, primary key +# user_id :integer +# provider :string default(""), not null +# uid :string default(""), not null +# created_at :datetime not null +# updated_at :datetime not null +# + +class Identity < ApplicationRecord + belongs_to :user, dependent: :destroy + validates :uid, presence: true, uniqueness: { scope: :provider } + validates :provider, presence: true + + def self.find_for_oauth(auth) + find_or_create_by(uid: auth.uid, provider: auth.provider) + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 6ef6db915..d495fc390 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -39,6 +39,7 @@ class User < ApplicationRecord include Settings::Extend + include Omniauthable ACTIVE_DURATION = 14.days @@ -51,7 +52,8 @@ class User < ApplicationRecord devise :registerable, :recoverable, :rememberable, :trackable, :validatable, :confirmable - devise :pam_authenticatable + devise :pam_authenticatable if Devise.pam_authentication + devise :omniauthable belongs_to :account, inverse_of: :user belongs_to :invite, counter_cache: :uses, optional: true diff --git a/app/validators/status_length_validator.rb b/app/validators/status_length_validator.rb index 79d17742a..2db9b29f6 100644 --- a/app/validators/status_length_validator.rb +++ b/app/validators/status_length_validator.rb @@ -23,6 +23,8 @@ class StatusLengthValidator < ActiveModel::Validator end def countable_text(status) + return '' if status.text.nil? + status.text.dup.tap do |new_text| new_text.gsub!(FetchLinkCardService::URL_PATTERN, 'x' * 23) new_text.gsub!(Account::MENTION_RE, '@\2') diff --git a/app/views/admin/settings/edit.html.haml b/app/views/admin/settings/edit.html.haml index 4f9115ed2..73fd5642e 100644 --- a/app/views/admin/settings/edit.html.haml +++ b/app/views/admin/settings/edit.html.haml @@ -19,6 +19,9 @@ = f.input :timeline_preview, as: :boolean, wrapper: :with_label, label: t('admin.settings.timeline_preview.title'), hint: t('admin.settings.timeline_preview.desc_html') .fields-group + = f.input :show_known_fediverse_at_about_page, as: :boolean, wrapper: :with_label, label: t('admin.settings.show_known_fediverse_at_about_page.title'), hint: t('admin.settings.show_known_fediverse_at_about_page.desc_html') + + .fields-group = f.input :show_staff_badge, as: :boolean, wrapper: :with_label, label: t('admin.settings.show_staff_badge.title'), hint: t('admin.settings.show_staff_badge.desc_html') .fields-group diff --git a/app/views/auth/confirmations/finish_signup.html.haml b/app/views/auth/confirmations/finish_signup.html.haml new file mode 100644 index 000000000..4b5161d6b --- /dev/null +++ b/app/views/auth/confirmations/finish_signup.html.haml @@ -0,0 +1,14 @@ +- content_for :page_title do + = t('auth.confirm_email') + += simple_form_for(current_user, as: 'user', url: finish_signup_path, html: { role: 'form'}) do |f| + - if @show_errors && current_user.errors.any? + #error_explanation + - current_user.errors.full_messages.each do |msg| + = msg + %br + + = f.input :email + + .actions + = f.submit t('auth.confirm_email'), class: 'button' diff --git a/app/views/auth/sessions/new.html.haml b/app/views/auth/sessions/new.html.haml index 3edb0d2d4..1c3a0b6b4 100644 --- a/app/views/auth/sessions/new.html.haml +++ b/app/views/auth/sessions/new.html.haml @@ -14,4 +14,13 @@ .actions = f.button :button, t('auth.login'), type: :submit +- if devise_mapping.omniauthable? and resource_class.omniauth_providers.any? + .simple_form.alternative-login + %h4= t('auth.or_log_in_with') + + .actions + - resource_class.omniauth_providers.each do |provider| + = link_to omniauth_authorize_path(resource_name, provider), class: "button button-#{provider}" do + = t("auth.providers.#{provider}", default: provider.to_s.chomp("_oauth2").capitalize) + .form-footer= render 'auth/shared/links' |