Commit dc7c5ffe authored by Benjamin Neff's avatar Benjamin Neff

Merge branch 'release/0.7.11.0'

parents 663da1ef 5aec9b96
......@@ -27,10 +27,16 @@ Metrics/ClassLength:
Metrics/ModuleLength:
Max: 1500
# Raise AbcSize from 15 to 20
# Raise complexity metrics
Metrics/AbcSize:
Max: 20
Metrics/CyclomaticComplexity:
Max: 20
Metrics/PerceivedComplexity:
Max: 20
# Some blocks are longer.
Metrics/BlockLength:
ExcludedMethods:
......
# 0.7.11.0
## Refactor
* Enable paranoid mode for devise [#8003](https://github.com/diaspora/diaspora/pull/8003)
* Refactor likes cucumber test [#8002](https://github.com/diaspora/diaspora/pull/8002)
## Bug fixes
* Fix old photos without remote url for export [#8012](https://github.com/diaspora/diaspora/pull/8012)
## Features
* Add a manifest.json file as a first step to make diaspora\* a Progressive Web App [#7998](https://github.com/diaspora/diaspora/pull/7998)
* Allow `web+diaspora://` links to link to a profile with only the diaspora ID [#8000](https://github.com/diaspora/diaspora/pull/8000)
* Support TOTP two factor authentication [#7751](https://github.com/diaspora/diaspora/pull/7751)
# 0.7.10.0
## Refactor
......
......@@ -2,32 +2,34 @@
source "https://rubygems.org"
gem "rails", "5.1.6"
gem "rails", "5.1.6.2"
# Legacy Rails features, remove me!
# responders (class level)
gem "responders", "2.4.0"
gem "responders", "2.4.1"
# Appserver
gem "unicorn", "5.4.1", require: false
gem "unicorn", "5.5.0", require: false
gem "unicorn-worker-killer", "0.4.4"
# Federation
gem "diaspora_federation-json_schema", "0.2.5"
gem "diaspora_federation-rails", "0.2.5"
gem "diaspora_federation-json_schema", "0.2.6"
gem "diaspora_federation-rails", "0.2.6"
# API and JSON
gem "acts_as_api", "1.0.1"
gem "json", "2.1.0"
gem "json", "2.2.0"
gem "json-schema", "2.8.1"
# Authentication
gem "devise", "4.5.0"
gem "devise", "4.6.1"
gem "devise-two-factor", "3.0.3"
gem "devise_lastseenable", "0.0.6"
gem "rqrcode", "0.10.1"
# Captcha
......@@ -36,15 +38,15 @@ gem "simple_captcha2", "0.4.3", require: "simple_captcha"
# Background processing
gem "redis", "3.3.5" # Pinned to 3.3.x because of https://github.com/antirez/redis/issues/4272
gem "sidekiq", "5.2.3"
gem "sidekiq", "5.2.5"
# Scheduled processing
gem "sidekiq-cron", "1.0.4"
gem "sidekiq-cron", "1.1.0"
# Compression
gem "uglifier", "4.1.19"
gem "uglifier", "4.1.20"
# Configuration
......@@ -57,7 +59,7 @@ gem "rack-cors", "1.0.2", require: "rack/cors"
# CSS
gem "autoprefixer-rails", "8.6.5"
gem "bootstrap-sass", "3.3.7"
gem "bootstrap-sass", "3.4.1"
gem "bootstrap-switch-rails", "3.3.3" # 3.3.4 is broken, see https://github.com/Bttstrp/bootstrap-switch/issues/691
gem "compass-rails", "3.1.0"
gem "sass-rails", "5.0.7"
......@@ -69,17 +71,17 @@ group :mysql, optional: true do
gem "mysql2", "0.5.2"
end
group :postgresql, optional: true do
gem "pg", "1.1.3"
gem "pg", "1.1.4"
end
gem "activerecord-import", "0.27.0"
gem "activerecord-import", "1.0.1"
# File uploading
gem "carrierwave", "1.2.3"
gem "fog-aws", "3.3.0"
gem "mini_magick", "4.9.2"
gem "carrierwave", "1.3.1"
gem "fog-aws", "3.4.0"
gem "mini_magick", "4.9.3"
# GUID generation
gem "uuid", "2.3.9"
......@@ -90,7 +92,7 @@ gem "entypo-rails", "3.0.0"
# JavaScript
gem "handlebars_assets", "0.23.2"
gem "handlebars_assets", "0.23.3"
gem "jquery-rails", "4.3.3"
gem "js-routes", "1.4.4"
gem "js_image_paths", "0.1.1"
......@@ -129,7 +131,7 @@ gem "markdown-it-html5-embed", "1.0.0"
gem "http_accept_language", "2.1.1"
gem "i18n-inflector-rails", "1.0.7"
gem "rails-i18n", "5.1.2"
gem "rails-i18n", "5.1.3"
# Mail
......@@ -140,7 +142,7 @@ gem "leaflet-rails", "1.3.1"
# Parsing
gem "nokogiri", "1.8.5"
gem "nokogiri", "1.10.1"
gem "open_graph_reader", "0.6.2" # also update User-Agent in features/support/webmock.rb
gem "redcarpet", "3.4.0"
gem "ruby-oembed", "0.12.0"
......@@ -152,11 +154,11 @@ gem "string-direction", "1.2.1"
# Security Headers
gem "secure_headers", "6.0.0"
gem "secure_headers", "6.1.0"
# Services
gem "omniauth", "1.8.1"
gem "omniauth", "1.9.0"
gem "omniauth-tumblr", "1.2"
gem "omniauth-twitter", "1.4.0"
gem "omniauth-wordpress", "0.2.2"
......@@ -180,7 +182,7 @@ gem "acts-as-taggable-on", "6.0.0"
# URIs and HTTP
gem "addressable", "2.5.2", require: "addressable/uri"
gem "faraday", "0.15.3"
gem "faraday", "0.15.4"
gem "faraday_middleware", "0.12.2"
gem "faraday-cookie_jar", "0.0.6"
gem "typhoeus", "1.3.1"
......@@ -188,10 +190,10 @@ gem "typhoeus", "1.3.1"
# Views
gem "gon", "6.2.1"
gem "hamlit", "2.9.1"
gem "hamlit", "2.9.2"
gem "mobile-fu", "1.4.0"
gem "rails-timeago", "2.16.0"
gem "will_paginate", "3.1.6"
gem "rails-timeago", "2.17.1"
gem "will_paginate", "3.1.7"
# Logging
......@@ -234,7 +236,7 @@ group :production do # we don"t install these on travis to speed up test runs
# Third party asset hosting
gem "asset_sync", "2.5.0", require: false
gem "asset_sync", "2.7.0", require: false
end
group :development do
......@@ -243,7 +245,7 @@ group :development do
gem "guard-rspec", "4.7.3", require: false
gem "guard-rubocop", "1.3.0", require: false
gem "rb-fsevent", "0.10.3", require: false
gem "rb-inotify", "0.9.10", require: false
gem "rb-inotify", "0.10.0", require: false
# Linters
gem "haml_lint", "0.28.0", require: false
......@@ -252,7 +254,7 @@ group :development do
gem "pronto-haml", "0.9.0", require: false
gem "pronto-rubocop", "0.9.1", require: false
gem "pronto-scss", "0.9.1", require: false
gem "rubocop", "0.60.0", require: false
gem "rubocop", "0.66.0", require: false
# Preloading environment
......@@ -280,7 +282,7 @@ group :test do
# Cucumber (integration tests)
gem "capybara", "3.11.1"
gem "capybara", "3.15.0"
gem "database_cleaner", "1.7.0"
gem "poltergeist", "1.18.1"
......@@ -289,11 +291,11 @@ group :test do
# General helpers
gem "factory_girl_rails", "4.8.0"
gem "shoulda-matchers", "3.1.2"
gem "shoulda-matchers", "4.0.1"
gem "timecop", "0.9.1"
gem "webmock", "3.4.2", require: false
gem "webmock", "3.5.1", require: false
gem "diaspora_federation-test", "0.2.5"
gem "diaspora_federation-test", "0.2.6"
# Coverage
gem "coveralls", "0.8.22", require: false
......@@ -301,7 +303,7 @@ end
group :development, :test do
# RSpec (unit tests, some integration tests)
gem "rspec-rails", "3.8.1"
gem "rspec-rails", "3.8.2"
# Cucumber (integration tests)
gem "cucumber-rails", "1.6.0", require: false
......@@ -313,5 +315,5 @@ group :development, :test do
gem "sinon-rails", "1.15.0"
# For `assigns` in controller specs
gem "rails-controller-testing", "1.0.2"
gem "rails-controller-testing", "1.0.4"
end
This diff is collapsed.
......@@ -10,14 +10,15 @@ body {
.page-contacts,
.page-conversations,
.page-notifications,
.page-people.action-show,
.page-people.action-contacts,
.page-people.action-show,
.page-photos,
.page-posts,
.page-profiles.action-edit,
.page-services.action-index,
.page-streams,
.page-tags,
.page-two_factor_authentications,
.page-user_applications,
.page-users.action-edit,
.page-users.action-update,
......
......@@ -93,5 +93,10 @@ textarea {
}
::placeholder { text-transform: uppercase; }
p {
margin-top: .5rem;
text-align: center;
}
}
}
.page-sessions.action-new,
.page-sessions.action-create,
.page-passwords.action-new,
.page-passwords.action-edit {
padding-top: 25px;
......
......@@ -27,6 +27,7 @@ class ApplicationController < ActionController::Base
before_action :gon_set_current_user
before_action :gon_set_appconfig
before_action :gon_set_preloads
before_action :configure_permitted_parameters, if: :devise_controller?
inflection_method grammatical_gender: :gender
......@@ -182,4 +183,10 @@ class ApplicationController < ActionController::Base
return unless gon.preloads.nil?
gon.preloads = {}
end
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_in, keys: [:otp_attempt])
end
end
# frozen_string_literal: true
class ManifestController < ApplicationController
def show # rubocop:disable Metrics/MethodLength
render json: {
short_name: AppConfig.settings.pod_name,
name: AppConfig.settings.pod_name,
description: "diaspora* is a free, decentralized and privacy-respecting social network",
icons: [
{
src: helpers.image_path("branding/logos/app-icon.png"),
type: "image/png",
sizes: "192x192"
},
{
src: helpers.image_path("branding/logos/app-icon-512.png"),
type: "image/png",
sizes: "512x512"
}
],
start_url: "/",
background_color: "#000000",
display: "standalone",
theme_color: "#000000"
}
end
end
......@@ -5,10 +5,54 @@
# the COPYRIGHT file.
class SessionsController < Devise::SessionsController
after_action :reset_authentication_token, only: [:create]
before_action :reset_authentication_token, only: [:destroy]
# rubocop:disable Rails/LexicallyScopedActionFilter
before_action :authenticate_with_2fa, only: :create
after_action :reset_authentication_token, only: :create
before_action :reset_authentication_token, only: :destroy
# rubocop:enable Rails/LexicallyScopedActionFilter
def find_user
return User.find(session[:otp_user_id]) if session[:otp_user_id]
User.find_for_authentication(username: params[:user][:username]) if params[:user][:username]
end
def authenticate_with_2fa
self.resource = find_user
u = find_user
return true unless u&.otp_required_for_login?
if params[:user][:otp_attempt].present? && session[:otp_user_id]
authenticate_with_two_factor_via_otp(u)
elsif u&.valid_password?(params[:user][:password])
prompt_for_two_factor(u)
end
end
def valid_otp_attempt?(user)
user.validate_and_consume_otp!(params[:user][:otp_attempt]) ||
user.invalidate_otp_backup_code!(params[:user][:otp_attempt])
rescue OpenSSL::Cipher::CipherError => _error
false
end
def authenticate_with_two_factor_via_otp(user)
if valid_otp_attempt?(user)
session.delete(:otp_user_id)
sign_in(user)
else
flash.now[:alert] = "Invalid token"
prompt_for_two_factor(user)
end
end
def prompt_for_two_factor(user)
session[:otp_user_id] = user.id
render :two_factor
end
def reset_authentication_token
current_user.reset_authentication_token! unless current_user.nil?
current_user&.reset_authentication_token!
end
end
# frozen_string_literal: true
class TwoFactorAuthenticationsController < ApplicationController
before_action :authenticate_user!
before_action :verify_otp_required, only: [:create]
def show
@user = current_user
end
def create
current_user.otp_secret = User.generate_otp_secret(32)
current_user.save!
redirect_to confirm_two_factor_authentication_path
end
def confirm_2fa
redirect_to two_factor_authentication_path if current_user.otp_required_for_login?
end
def confirm_and_activate_2fa
if current_user.validate_and_consume_otp!(params[:user][:code])
current_user.otp_required_for_login = true
current_user.save!
flash[:notice] = t("two_factor_auth.flash.success_activation")
redirect_to recovery_codes_two_factor_authentication_path
else
flash[:alert] = t("two_factor_auth.flash.error_token")
redirect_to confirm_two_factor_authentication_path
end
end
def recovery_codes
@recovery_codes = current_user.generate_otp_backup_codes!
current_user.save!
end
def destroy
if current_user.valid_password?(params[:two_factor_authentication][:password])
current_user.otp_required_for_login = false
current_user.save!
flash[:notice] = t("two_factor_auth.flash.success_deactivation")
else
flash[:alert] = t("users.destroy.wrong_password")
end
redirect_to two_factor_authentication_path
end
private
def verify_otp_required
redirect_to two_factor_authentication_path if current_user.otp_required_for_login?
end
end
......@@ -152,6 +152,8 @@ class UsersController < ApplicationController
:auto_follow_back_aspect_id,
:getting_started,
:post_default_public,
:otp_required_for_login,
:otp_secret,
email_preferences: UserPreference::VALID_EMAIL_TYPES.map(&:to_sym)
)
end
......
......@@ -72,4 +72,9 @@ module ApplicationHelper
buf << [nonced_javascript_tag("$.fx.off = true;")] if Rails.env.test?
buf.join("\n").html_safe
end
def qrcode_uri
label = current_user.username
current_user.otp_provisioning_uri(label, issuer: AppConfig.environment.url)
end
end
......@@ -19,7 +19,14 @@ class User < ApplicationRecord
scope :halfyear_actives, ->(time = Time.now) { logged_in_since(time - 6.month) }
scope :active, -> { joins(:person).where(people: {closed_account: false}) }
devise :database_authenticatable, :registerable,
attr_encrypted :otp_secret, if: false, prefix: "plain_"
devise :two_factor_authenticatable,
:two_factor_backupable,
otp_backup_code_length: 16,
otp_number_of_backup_codes: 10
devise :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:lockable, :lastseenable, :lock_strategy => :none, :unlock_strategy => :none
......@@ -42,6 +49,7 @@ class User < ApplicationRecord
validate :no_person_with_same_username
serialize :hidden_shareables, Hash
serialize :otp_backup_codes, Array
has_one :person, inverse_of: :owner, foreign_key: :owner_id
has_one :profile, through: :person
......
......@@ -10,7 +10,11 @@ class DiasporaLinkService
end
def find_or_fetch_entity
if type && guid
entity_finder.find || fetch_entity
elsif author
find_or_fetch_person
end
end
private
......@@ -28,6 +32,12 @@ class DiasporaLinkService
@entity_finder ||= Diaspora::EntityFinder.new(type, guid)
end
def find_or_fetch_person
Person.find_or_fetch_by_identifier(author)
rescue DiasporaFederation::Discovery::DiscoveryError
nil
end
def normalize
link.gsub!(%r{^web\+diaspora://}, "diaspora://") ||
link.gsub!(%r{^//}, "diaspora://") ||
......@@ -38,8 +48,10 @@ class DiasporaLinkService
def parse
normalize
match = DiasporaFederation::Federation::DiasporaUrlParser::DIASPORA_URL_REGEX.match(link)
@author = match[1]
@type = match[2]
@guid = match[3]
if match
@author, @type, @guid = match.captures
else
@author = %r{^diaspora://(#{Validation::Rule::DiasporaId::DIASPORA_ID_REGEX})$}u.match(link)&.captures&.first
end
end
end
%h2 Resend confirmation instructions
= form_for(resource, :as => resource_name, :url => confirmation_path(resource_name)) do |f|
= devise_error_messages!
= render partial: "devise/shared/error_messages"
%p
= f.label :email
%br/
= f.text_field :email
%p
= f.submit t('.resend_confirmation')
= render :partial => "devise/shared/links"
= render partial: "devise/shared/links"
......@@ -6,7 +6,7 @@
.login-form
.login-container
= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f|
= devise_error_messages!
= render partial: "devise/shared/error_messages", formats: [:html]
= f.hidden_field :reset_password_token
%fieldset
%legend
......
......@@ -9,10 +9,6 @@
= AppConfig.settings.pod_name
= form_for(resource, as: resource_name, url: password_path(resource_name), html: {class: "form-horizontal block-form"}, autocomplete: 'off') do |f|
- if !devise_error_messages!.empty?
%legend
%i
= t('devise.passwords.new.no_account') # this is an error message and should not be displayed as a legend
%fieldset
%label#emailLabel.sr-only{for: "user_email"}
= t("devise.passwords.new.email")
......
......@@ -9,7 +9,7 @@
%fieldset
%legend
=t('devise.passwords.new.forgot_password')
- unless devise_error_messages!.empty?
- unless resource.errors.empty?
%i= t('devise.passwords.new.no_account')
.control-group
......
%h2 Resend unlock instructions
= form_for(resource, :as => resource_name, :url => unlock_path(resource_name)) do |f|
= devise_error_messages!
= render partial: "devise/shared/error_messages"
%p
= f.label :email
%br/
= f.text_field :email
%p
= f.submit t('.resend_unlock')
= render :partial => "devise/shared/links"
= render partial: "devise/shared/links"
......@@ -4,7 +4,7 @@
%meta{charset: "utf-8"}/
= content_for?(:meta_data) ? yield(:meta_data) : metas_tags
%meta{content: "yes", name: "mobile-web-app-capable"}/
%link{rel: "manifest", href: "/manifest.json"}
/ favicon
/ For Apple devices
......
......@@ -12,7 +12,7 @@
= load_javascript_locales
= include_color_theme
= render "head"
= render "layouts/head"
= translation_missing_warnings
%body{class: "page-#{controller_name} action-#{action_name}"}
......
......@@ -9,7 +9,7 @@
= javascript_include_tag "mobile/mobile"
= load_javascript_locales
= render "head"
= render "layouts/head"
= include_color_theme "mobile"
%meta{name: "viewport", content: "width=device-width, minimum-scale=1, maximum-scale=1, user-scalable=no"}/
......@@ -17,11 +17,6 @@
%meta{name: "MobileOptimized", content: "320"}/
%meta{"http-equiv" => "cleartype", :content => "on"}/
/ iOS mobile web app indicator
/ NOTE(we will enable these once we don't have to rely on back/forward buttons anymore)
/%meta{:name => "apple-mobile-web-app-capable", :content => "yes"}
/%link{:rel => "apple-touch-startup-image", :href => "/images/apple-splash.png"}
%body
#app
= render "layouts/header"
......
- content_for :page_title do
= AppConfig.settings.pod_name + " - " + t("two_factor_auth.title")
.container#twofa
.text-center
.logos-asterisk
%h1
= t("two_factor_auth.title")
= form_for resource, as: resource_name,
url: session_path(resource_name),
html: {class: "block-form"},
method: :post do |f|
%fieldset
%label.sr-only#otp-label{for: "otp_attempt"}
= t("two_factor_auth.input_token.label")
%i.entypo-lock
= f.text_field :otp_attempt,
type: :text,
placeholder: t("two_factor_auth.input_token.placeholder"),
required: true,
autofocus: true,
class: "input-block-level form-control"
%p= t "two_factor_auth.recovery.reminder"
.actions
= f.button t("devise.sessions.new.sign_in"),
type: :submit,
class: "btn btn-large btn-block btn-primary"
......@@ -9,6 +9,8 @@
class: request.path == edit_user_path ? "list-group-item active" : "list-group-item"