Merge branch 'master' of github.com:discourse/discourse
This commit is contained in:
commit
3b34ab8cab
1
Gemfile
1
Gemfile
|
@ -97,6 +97,7 @@ gem 'openid-redis-store'
|
||||||
gem 'omniauth-facebook'
|
gem 'omniauth-facebook'
|
||||||
gem 'omniauth-twitter'
|
gem 'omniauth-twitter'
|
||||||
gem 'omniauth-github'
|
gem 'omniauth-github'
|
||||||
|
gem 'omniauth-oauth2', require: false
|
||||||
gem 'omniauth-browserid', git: 'https://github.com/callahad/omniauth-browserid.git', branch: 'observer_api'
|
gem 'omniauth-browserid', git: 'https://github.com/callahad/omniauth-browserid.git', branch: 'observer_api'
|
||||||
gem 'omniauth-cas'
|
gem 'omniauth-cas'
|
||||||
gem 'oj'
|
gem 'oj'
|
||||||
|
|
|
@ -497,6 +497,7 @@ DEPENDENCIES
|
||||||
omniauth-cas
|
omniauth-cas
|
||||||
omniauth-facebook
|
omniauth-facebook
|
||||||
omniauth-github
|
omniauth-github
|
||||||
|
omniauth-oauth2
|
||||||
omniauth-openid
|
omniauth-openid
|
||||||
omniauth-twitter
|
omniauth-twitter
|
||||||
openid-redis-store
|
openid-redis-store
|
||||||
|
|
|
@ -50,8 +50,8 @@ Discourse = Ember.Application.createWithMixins(Discourse.Ajax, {
|
||||||
|
|
||||||
faviconChanged: function() {
|
faviconChanged: function() {
|
||||||
if(Discourse.User.currentProp('dynamic_favicon')) {
|
if(Discourse.User.currentProp('dynamic_favicon')) {
|
||||||
$.faviconNotify(
|
new Favcount(Discourse.SiteSettings.favicon_url).set(
|
||||||
Discourse.SiteSettings.favicon_url, this.get('notifyCount')
|
this.get('notifyCount')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}.observes('notifyCount'),
|
}.observes('notifyCount'),
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
* favcount.js v1.0.1
|
||||||
|
* http://chrishunt.co/favcount
|
||||||
|
* Dynamically updates the favicon with a number.
|
||||||
|
*
|
||||||
|
* Copyright 2013, Chris Hunt
|
||||||
|
* Released under the MIT license
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function(){
|
||||||
|
function Favcount(icon) {
|
||||||
|
this.icon = icon;
|
||||||
|
this.canvas = document.createElement('canvas');
|
||||||
|
}
|
||||||
|
|
||||||
|
Favcount.prototype.set = function(count) {
|
||||||
|
var self = this,
|
||||||
|
img = document.createElement('img');
|
||||||
|
|
||||||
|
if (self.canvas.getContext) {
|
||||||
|
img.onload = function() {
|
||||||
|
drawCanvas(self.canvas, img, normalize(count));
|
||||||
|
};
|
||||||
|
|
||||||
|
img.src = this.icon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalize(count) {
|
||||||
|
count = Math.round(count);
|
||||||
|
|
||||||
|
if (isNaN(count) || count < 1) {
|
||||||
|
return '';
|
||||||
|
} else if (count < 10) {
|
||||||
|
return ' ' + count;
|
||||||
|
} else if (count > 99) {
|
||||||
|
return '99';
|
||||||
|
} else {
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawCanvas(canvas, img, count) {
|
||||||
|
var head = document.getElementsByTagName('head')[0],
|
||||||
|
favicon = document.createElement('link'),
|
||||||
|
multiplier, fontSize, context, xOffset, yOffset;
|
||||||
|
|
||||||
|
favicon.rel = 'icon';
|
||||||
|
|
||||||
|
// Scale the canvas based on favicon size
|
||||||
|
multiplier = img.width / 16;
|
||||||
|
fontSize = multiplier * 11;
|
||||||
|
xOffset = multiplier;
|
||||||
|
yOffset = multiplier * 11;
|
||||||
|
|
||||||
|
canvas.height = canvas.width = img.width;
|
||||||
|
|
||||||
|
context = canvas.getContext('2d');
|
||||||
|
context.drawImage(img, 0, 0);
|
||||||
|
context.font = 'bold ' + fontSize + 'px "helvetica", sans-serif';
|
||||||
|
|
||||||
|
// Draw background for contrast
|
||||||
|
context.fillStyle = '#FFF';
|
||||||
|
context.fillText(count, xOffset, yOffset);
|
||||||
|
context.fillText(count, xOffset + 2, yOffset);
|
||||||
|
context.fillText(count, xOffset, yOffset + 2);
|
||||||
|
context.fillText(count, xOffset + 2, yOffset + 2);
|
||||||
|
|
||||||
|
// Draw count in foreground
|
||||||
|
context.fillStyle = '#000';
|
||||||
|
context.fillText(count, xOffset + 1, yOffset + 1);
|
||||||
|
|
||||||
|
// Replace the favicon
|
||||||
|
favicon.href = canvas.toDataURL('image/png');
|
||||||
|
head.removeChild(document.querySelector('link[rel=icon]'));
|
||||||
|
head.appendChild(favicon);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Favcount = Favcount;
|
||||||
|
}).call(this);
|
||||||
|
|
||||||
|
(function(){
|
||||||
|
Favcount.VERSION = '1.0.1';
|
||||||
|
}).call(this);
|
|
@ -1,53 +0,0 @@
|
||||||
/**
|
|
||||||
* jQuery Favicon Notify
|
|
||||||
*
|
|
||||||
* Updates the favicon with a number to notify the user of changes.
|
|
||||||
*
|
|
||||||
* iconUrl: Url of favicon image or icon
|
|
||||||
* count: Integer count to place above favicon
|
|
||||||
*
|
|
||||||
* $.faviconNotify(iconUrl, count)
|
|
||||||
*/
|
|
||||||
(function($){
|
|
||||||
$.faviconNotify = function(iconUrl, count){
|
|
||||||
var canvas = canvas || $('<canvas />')[0],
|
|
||||||
img = $('<img />')[0],
|
|
||||||
multiplier, fontSize, context, xOffset, yOffset;
|
|
||||||
|
|
||||||
if (canvas.getContext) {
|
|
||||||
if (count < 1) { count = '' }
|
|
||||||
else if (count < 10) { count = ' ' + count }
|
|
||||||
else if (count > 99) { count = '99' }
|
|
||||||
|
|
||||||
img.onload = function () {
|
|
||||||
canvas.height = canvas.width = this.width;
|
|
||||||
multiplier = (this.width / 16);
|
|
||||||
|
|
||||||
fontSize = multiplier * 11;
|
|
||||||
xOffset = multiplier;
|
|
||||||
yOffset = multiplier * 11;
|
|
||||||
|
|
||||||
context = canvas.getContext('2d');
|
|
||||||
context.drawImage(this, 0, 0);
|
|
||||||
context.font = 'bold ' + fontSize + 'px "helvetica", sans-serif';
|
|
||||||
|
|
||||||
context.fillStyle = '#FFF';
|
|
||||||
context.fillText(count, xOffset, yOffset);
|
|
||||||
context.fillText(count, xOffset + 2, yOffset);
|
|
||||||
context.fillText(count, xOffset, yOffset + 2);
|
|
||||||
context.fillText(count, xOffset + 2, yOffset + 2);
|
|
||||||
|
|
||||||
context.fillStyle = '#000';
|
|
||||||
context.fillText(count, xOffset + 1, yOffset + 1);
|
|
||||||
|
|
||||||
$('link[rel$=icon]').remove();
|
|
||||||
$('head').append(
|
|
||||||
$('<link rel="shortcut icon" type="image/x-icon"/>').attr(
|
|
||||||
'href', canvas.toDataURL('image/png')
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
img.src = iconUrl;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})(jQuery);
|
|
|
@ -29,6 +29,10 @@ class Users::OmniauthCallbacksController < ApplicationController
|
||||||
create_or_sign_on_user_using_openid request.env["omniauth.auth"]
|
create_or_sign_on_user_using_openid request.env["omniauth.auth"]
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
|
elsif p.name == provider && p.type == :oauth2
|
||||||
|
create_or_sign_on_user_using_oauth2 request.env["omniauth.auth"]
|
||||||
|
found = true
|
||||||
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -194,6 +198,58 @@ class Users::OmniauthCallbacksController < ApplicationController
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create_or_sign_on_user_using_oauth2(auth_token)
|
||||||
|
oauth2_provider = auth_token[:provider]
|
||||||
|
oauth2_uid = auth_token[:uid]
|
||||||
|
data = auth_token[:info]
|
||||||
|
email = data[:email]
|
||||||
|
name = data[:name]
|
||||||
|
|
||||||
|
oauth2_user_info = Oauth2UserInfo.where(uid: oauth2_uid, provider: oauth2_provider).first
|
||||||
|
|
||||||
|
if oauth2_user_info.blank? && user = User.find_by_email(email)
|
||||||
|
# TODO is only safe if we trust our oauth2 provider to return an email
|
||||||
|
# legitimately owned by our user
|
||||||
|
oauth2_user_info = Oauth2UserInfo.create(uid: oauth2_uid,
|
||||||
|
provider: oauth2_provider,
|
||||||
|
name: name,
|
||||||
|
email: name,
|
||||||
|
user: user)
|
||||||
|
end
|
||||||
|
|
||||||
|
authenticated = oauth2_user_info.present?
|
||||||
|
|
||||||
|
if authenticated
|
||||||
|
user = oauth2_user_info.user
|
||||||
|
|
||||||
|
# If we have to approve users
|
||||||
|
if Guardian.new(user).can_access_forum?
|
||||||
|
log_on_user(user)
|
||||||
|
@data = {authenticated: true}
|
||||||
|
else
|
||||||
|
@data = {awaiting_approval: true}
|
||||||
|
end
|
||||||
|
else
|
||||||
|
@data = {
|
||||||
|
email: email,
|
||||||
|
name: User.suggest_name(name),
|
||||||
|
username: UserNameSuggester.suggest(email),
|
||||||
|
email_valid: true ,
|
||||||
|
auth_provider: oauth2_provider
|
||||||
|
}
|
||||||
|
|
||||||
|
session[:authentication] = {
|
||||||
|
oauth2: {
|
||||||
|
provider: oauth2_provider,
|
||||||
|
uid: oauth2_uid,
|
||||||
|
},
|
||||||
|
name: name,
|
||||||
|
email: @data[:email],
|
||||||
|
email_valid: @data[:email_valid]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
def create_or_sign_on_user_using_openid(auth_token)
|
def create_or_sign_on_user_using_openid(auth_token)
|
||||||
|
|
||||||
|
|
|
@ -432,6 +432,16 @@ class UsersController < ApplicationController
|
||||||
github_user_id: auth[:github_user_id]
|
github_user_id: auth[:github_user_id]
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if oauth2_auth?(auth)
|
||||||
|
Oauth2UserInfo.create(
|
||||||
|
uid: auth[:oauth2][:uid],
|
||||||
|
provider: auth[:oauth2][:provider],
|
||||||
|
name: auth[:name],
|
||||||
|
email: auth[:email],
|
||||||
|
user_id: user.id
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def twitter_auth?(auth)
|
def twitter_auth?(auth)
|
||||||
|
@ -448,4 +458,9 @@ class UsersController < ApplicationController
|
||||||
auth[:github_user_id] && auth[:github_screen_name] &&
|
auth[:github_user_id] && auth[:github_screen_name] &&
|
||||||
GithubUserInfo.find_by_github_user_id(auth[:github_user_id]).nil?
|
GithubUserInfo.find_by_github_user_id(auth[:github_user_id]).nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def oauth2_auth?(auth)
|
||||||
|
auth[:oauth2].is_a?(Hash) && auth[:oauth2][:provider] && auth[:oauth2][:uid] &&
|
||||||
|
Oauth2UserInfo.where(provider: auth[:oauth2][:provider], uid: auth[:oauth2][:uid]).empty?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,9 +1,17 @@
|
||||||
class Category < ActiveRecord::Base
|
class Category < ActiveRecord::Base
|
||||||
belongs_to :topic, dependent: :destroy
|
belongs_to :topic, dependent: :destroy
|
||||||
belongs_to :topic_only_relative_url,
|
if rails4?
|
||||||
|
belongs_to :topic_only_relative_url,
|
||||||
|
-> { select "id, title, slug" },
|
||||||
|
class_name: "Topic",
|
||||||
|
foreign_key: "topic_id"
|
||||||
|
else
|
||||||
|
belongs_to :topic_only_relative_url,
|
||||||
select: "id, title, slug",
|
select: "id, title, slug",
|
||||||
class_name: "Topic",
|
class_name: "Topic",
|
||||||
foreign_key: "topic_id"
|
foreign_key: "topic_id"
|
||||||
|
end
|
||||||
|
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
|
|
||||||
has_many :topics
|
has_many :topics
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
class Oauth2UserInfo < ActiveRecord::Base
|
||||||
|
belongs_to :user
|
||||||
|
|
||||||
|
end
|
|
@ -34,6 +34,7 @@ class User < ActiveRecord::Base
|
||||||
has_one :twitter_user_info, dependent: :destroy
|
has_one :twitter_user_info, dependent: :destroy
|
||||||
has_one :github_user_info, dependent: :destroy
|
has_one :github_user_info, dependent: :destroy
|
||||||
has_one :cas_user_info, dependent: :destroy
|
has_one :cas_user_info, dependent: :destroy
|
||||||
|
has_one :oauth2_user_info, dependent: :destroy
|
||||||
belongs_to :approved_by, class_name: 'User'
|
belongs_to :approved_by, class_name: 'User'
|
||||||
|
|
||||||
has_many :group_users
|
has_many :group_users
|
||||||
|
|
|
@ -25,6 +25,14 @@ Rails.application.config.middleware.use OmniAuth::Builder do
|
||||||
:store => OpenID::Store::Redis.new($redis),
|
:store => OpenID::Store::Redis.new($redis),
|
||||||
:require => "omniauth-openid"
|
:require => "omniauth-openid"
|
||||||
}.merge(p.options)
|
}.merge(p.options)
|
||||||
|
elsif p.type == :oauth2
|
||||||
|
provider :oauth2,
|
||||||
|
p.options[:client_id],
|
||||||
|
p.options[:client_secret],
|
||||||
|
{
|
||||||
|
:name => p.name,
|
||||||
|
:require => "omniauth-oauth2"
|
||||||
|
}.merge(p.options)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
class CreateOauth2UserInfos < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
create_table :oauth2_user_infos do |t|
|
||||||
|
t.integer :user_id, null: false
|
||||||
|
t.string :uid, null: false
|
||||||
|
t.string :provider, null: false
|
||||||
|
t.string :email
|
||||||
|
t.string :name
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index :oauth2_user_infos, [:uid, :provider], unique: true
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,14 @@
|
||||||
|
unless Rails.version =~ /^4/
|
||||||
|
module ActiveRecord
|
||||||
|
class Relation
|
||||||
|
# Patch Rails 3 ActiveRecord::Relation to noop on Rails 4 references
|
||||||
|
# thereby getting code that works for rails 3 and 4 without
|
||||||
|
# deprecation warnings
|
||||||
|
|
||||||
|
def references(*args)
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -118,9 +118,7 @@ class Search
|
||||||
.order("topics_month DESC")
|
.order("topics_month DESC")
|
||||||
.secured(@guardian)
|
.secured(@guardian)
|
||||||
.limit(@limit)
|
.limit(@limit)
|
||||||
if rails4?
|
.references(:category_search_data)
|
||||||
categories = categories.references(:category_search_data)
|
|
||||||
end
|
|
||||||
|
|
||||||
categories.each do |c|
|
categories.each do |c|
|
||||||
@results.add_result(SearchResult.from_category(c))
|
@results.add_result(SearchResult.from_category(c))
|
||||||
|
@ -133,9 +131,7 @@ class Search
|
||||||
.order("CASE WHEN username_lower = '#{@original_term.downcase}' THEN 0 ELSE 1 END")
|
.order("CASE WHEN username_lower = '#{@original_term.downcase}' THEN 0 ELSE 1 END")
|
||||||
.order("last_posted_at DESC")
|
.order("last_posted_at DESC")
|
||||||
.limit(@limit)
|
.limit(@limit)
|
||||||
if rails4?
|
.references(:user_search_data)
|
||||||
users = users.references(:user_search_data)
|
|
||||||
end
|
|
||||||
|
|
||||||
users.each do |u|
|
users.each do |u|
|
||||||
@results.add_result(SearchResult.from_user(u))
|
@results.add_result(SearchResult.from_user(u))
|
||||||
|
@ -148,10 +144,7 @@ class Search
|
||||||
.where("topics.deleted_at" => nil)
|
.where("topics.deleted_at" => nil)
|
||||||
.where("topics.visible")
|
.where("topics.visible")
|
||||||
.where("topics.archetype <> ?", Archetype.private_message)
|
.where("topics.archetype <> ?", Archetype.private_message)
|
||||||
|
.references(:post_search_data, {:topic => :category})
|
||||||
if rails4?
|
|
||||||
posts = posts.references(:post_search_data, {:topic => :category})
|
|
||||||
end
|
|
||||||
|
|
||||||
# If we have a search context, prioritize those posts first
|
# If we have a search context, prioritize those posts first
|
||||||
if @search_context.present?
|
if @search_context.present?
|
||||||
|
|
|
@ -212,23 +212,23 @@ class TopicQuery
|
||||||
end
|
end
|
||||||
|
|
||||||
result = result.listable_topics.includes(category: :topic_only_relative_url)
|
result = result.listable_topics.includes(category: :topic_only_relative_url)
|
||||||
result = result.where('categories.name is null or categories.name <> ?', options[:exclude_category]) if options[:exclude_category]
|
result = result.where('categories.name is null or categories.name <> ?', options[:exclude_category]).references(:categories) if options[:exclude_category]
|
||||||
result = result.where('categories.name = ?', options[:only_category]) if options[:only_category]
|
result = result.where('categories.name = ?', options[:only_category]).references(:categories) if options[:only_category]
|
||||||
result = result.limit(options[:per_page]) unless options[:limit] == false
|
result = result.limit(options[:per_page]) unless options[:limit] == false
|
||||||
result = result.visible if options[:visible] || @user.nil? || @user.regular?
|
result = result.visible if options[:visible] || @user.nil? || @user.regular?
|
||||||
result = result.where('topics.id <> ?', options[:except_topic_id]) if options[:except_topic_id]
|
result = result.where('topics.id <> ?', options[:except_topic_id]).references(:topics) if options[:except_topic_id]
|
||||||
result = result.offset(options[:page].to_i * options[:per_page]) if options[:page]
|
result = result.offset(options[:page].to_i * options[:per_page]) if options[:page]
|
||||||
|
|
||||||
if options[:topic_ids]
|
if options[:topic_ids]
|
||||||
result = result.where('topics.id in (?)', options[:topic_ids])
|
result = result.where('topics.id in (?)', options[:topic_ids]).references(:topics)
|
||||||
end
|
end
|
||||||
|
|
||||||
unless @user && @user.moderator?
|
unless @user && @user.moderator?
|
||||||
category_ids = @user.secure_category_ids if @user
|
category_ids = @user.secure_category_ids if @user
|
||||||
if category_ids.present?
|
if category_ids.present?
|
||||||
result = result.where('categories.read_restricted IS NULL OR categories.read_restricted = ? OR categories.id IN (?)', false, category_ids)
|
result = result.where('categories.read_restricted IS NULL OR categories.read_restricted = ? OR categories.id IN (?)', false, category_ids).references(:categories)
|
||||||
else
|
else
|
||||||
result = result.where('categories.read_restricted IS NULL OR categories.read_restricted = ?', false)
|
result = result.where('categories.read_restricted IS NULL OR categories.read_restricted = ?', false).references(:categories)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ module Trashable
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
included do
|
included do
|
||||||
default_scope where(with_deleted_scope_sql)
|
default_scope { where(with_deleted_scope_sql) }
|
||||||
|
|
||||||
# scope unscoped does not work
|
# scope unscoped does not work
|
||||||
belongs_to :deleted_by, class_name: 'User'
|
belongs_to :deleted_by, class_name: 'User'
|
||||||
|
@ -15,11 +15,8 @@ module Trashable
|
||||||
#
|
#
|
||||||
# with this in place Post.limit(10).with_deleted, will work as expected
|
# with this in place Post.limit(10).with_deleted, will work as expected
|
||||||
#
|
#
|
||||||
if rails4?
|
scope = rails4? ? self.all.with_default_scope : self.scoped.with_default_scope
|
||||||
scope = self.all.with_default_scope
|
|
||||||
else
|
|
||||||
scope = self.scoped.with_default_scope
|
|
||||||
end
|
|
||||||
scope.where_values.delete(with_deleted_scope_sql)
|
scope.where_values.delete(with_deleted_scope_sql)
|
||||||
scope
|
scope
|
||||||
end
|
end
|
||||||
|
|
|
@ -164,4 +164,25 @@ describe Users::OmniauthCallbacksController do
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'oauth2' do
|
||||||
|
before do
|
||||||
|
Discourse.stubs(:auth_providers).returns([stub(name: 'my_oauth2_provider', type: :oauth2)])
|
||||||
|
request.env["omniauth.auth"] = { uid: 'my-uid', provider: 'my-oauth-provider-domain.net', info: {email: 'eviltrout@made.up.email', name: 'Chatanooga'}}
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#create_or_sign_on_user_using_oauth2" do
|
||||||
|
context "User already exists" do
|
||||||
|
before do
|
||||||
|
User.stubs(:find_by_email).returns(Fabricate(:user))
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should create an OauthUserInfo" do
|
||||||
|
expect {
|
||||||
|
post :complete, provider: 'my_oauth2_provider'
|
||||||
|
}.to change { Oauth2UserInfo.count }.by(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue