Commit fa46a8f5fc858a9410e0f3b20753f5ea9e9710f2

Authored by Larissa Reis
2 parents 219aa665 f9165094

Merge branch 'external-person' into 'federation'

External person

Allow external users to login in a network that is federated with the user's network.

See merge request !951
Showing 53 changed files with 1289 additions and 275 deletions   Show diff stats
app/api/app.rb
1 1 require_dependency 'api/helpers'
2 2  
3 3 module Api
  4 + class NoosferoFederation < Grape::API
  5 + use Rack::JSONP
  6 + helpers Helpers
  7 + before { detect_stuff_by_domain }
  8 + format :json
  9 + content_type :json, "application/jrd+json"
  10 + prefix [ENV['RAILS_RELATIVE_URL_ROOT'], ".well-known"].compact.join('/')
  11 + mount Federation::Webfinger
  12 + end
  13 +
4 14 class App < Grape::API
5 15 use Rack::JSONP
6 16  
... ... @@ -23,6 +33,8 @@ module Api
23 33 end
24 34 end
25 35  
  36 + mount NoosferoFederation
  37 +
26 38 before { set_locale }
27 39 before { setup_multitenancy }
28 40 before { detect_stuff_by_domain }
... ...
app/api/federation/webfinger.rb 0 → 100644
... ... @@ -0,0 +1,108 @@
  1 +require 'rails/commands/server'
  2 +
  3 +module Api
  4 + module Federation
  5 + class Webfinger < Grape::API
  6 + get 'webfinger' do
  7 + result = generate_jrd
  8 + present result, with: Grape::Presenters::Presenter
  9 + end
  10 + end
  11 + end
  12 +end
  13 +
  14 +def generate_jrd
  15 + unless valid_domain?
  16 + not_found!
  17 + Rails.logger.error 'Domain Not Found'
  18 + end
  19 + if request_acct?
  20 + acct_hash
  21 + elsif valid_uri?(params[:resource])
  22 + uri_hash
  23 + end
  24 +end
  25 +
  26 +def domain
  27 + if request_acct?
  28 + params[:resource].split('@')[1]
  29 + else
  30 + params[:resource].split('/')[2]
  31 + end
  32 +end
  33 +
  34 +def valid_domain?
  35 + environment.domains.map(&:name).include? domain
  36 +end
  37 +
  38 +def request_acct?
  39 + params[:resource].include? 'acct:'
  40 +end
  41 +
  42 +def acct_hash
  43 + rails = Rails::Server.new
  44 + acct = Hash.new{|hash, key| hash[key] = Hash.new{|hash, key| hash[key] = Array.new}}
  45 + url = rails.options[:Host] + ':' + rails.options[:Port].to_s + '/'
  46 + person = Person.find_by_identifier(extract_person_identifier)
  47 +
  48 + if person.nil?
  49 + Rails.logger.error 'Person not found'
  50 + not_found!
  51 + else
  52 + acct[:subject] = params[:resource]
  53 + acct[:alias] = url + person.identifier
  54 + acct[:properties][:identifier] = person.identifier
  55 + acct[:properties][:created_at] = person.created_at
  56 + for blog in person.blogs do
  57 + acct[:links][:rel] << url + 'rel/' + blog.path
  58 + acct[:links][:href] << url + person.identifier + '/' + blog.path
  59 + end
  60 +
  61 + for galleries in person.articles.galleries do
  62 + acct[:links][:rel] << url + 'rel/' + galleries.path
  63 + acct[:links][:href] << url + person.identifier + '/' + galleries.path
  64 + end
  65 +
  66 + acct[:titles][:name] = person.name
  67 + end
  68 + acct
  69 +end
  70 +
  71 +def extract_person_identifier
  72 + params[:resource].split('@')[0].split(':')[1]
  73 +end
  74 +
  75 +def valid_uri?(url)
  76 + uri = URI.parse(url)
  77 + if uri.is_a?(URI::HTTP)
  78 + true
  79 + else
  80 + Rails.logger.error 'Bad URI Error'
  81 + not_found!
  82 + end
  83 +end
  84 +
  85 +def uri_hash
  86 + uri = {}
  87 + uri[:subject] = params[:resource]
  88 + entity = find_entity(params[:resource])
  89 + id = params[:resource].split('/').last.to_i
  90 + begin
  91 + uri[:properties] = entity.classify.constantize.find(id)
  92 + rescue ActiveRecord::RecordNotFound
  93 + Rails.logger.error "Entity: #{entity} with id: #{id} not found"
  94 + not_found!
  95 + end
  96 + uri
  97 +end
  98 +
  99 +def find_entity(uri)
  100 + possible_entity = uri.split('/')
  101 + possible_entity.map! { |entity| "#{entity}s" }
  102 + entity = (ActiveRecord::Base.connection.tables & possible_entity).first
  103 + unless entity
  104 + Rails.logger.error 'Entity not found on records'
  105 + not_found!
  106 + end
  107 + entity
  108 +end
... ...
app/api/helpers.rb
... ... @@ -10,6 +10,9 @@ module Api
10 10 include Noosfero::Plugin::HotSpot
11 11 include ForgotPasswordHelper
12 12 include SearchTermHelper
  13 + include ProfileImageHelper
  14 + include Noosfero::Gravatar
  15 + include ThemeLoaderHelper
13 16  
14 17 def set_locale
15 18 I18n.locale = (params[:lang] || request.env['HTTP_ACCEPT_LANGUAGE'] || 'en')
... ... @@ -412,7 +415,7 @@ module Api
412 415 content_type == 'TextArticle' ? Article.text_article_types : content_type
413 416 end
414 417 content_types.flatten.uniq
415   - end
  418 + end
416 419  
417 420 def period(from_date, until_date)
418 421 begin_period = from_date.nil? ? Time.at(0).to_datetime : from_date
... ...
app/api/v1/articles.rb
... ... @@ -62,7 +62,7 @@ module Api
62 62 { :success => true }
63 63 rescue Exception => exception
64 64 render_api_error!(_('The article couldn\'t be removed due to some problem. Please contact the administrator.'), 400)
65   - end
  65 + end
66 66 end
67 67  
68 68 desc 'Report a abuse and/or violent content in a article by id' do
... ...
app/api/v1/people.rb
... ... @@ -108,6 +108,23 @@ module Api
108 108 end
109 109 present output
110 110 end
  111 +
  112 + desc "Return the person profile picture (you can optionally pass a 'size' parameter)"
  113 + get ":id/icon" do
  114 + person = environment.people.find(params[:id])
  115 +
  116 + size = params[:size] || :portrait
  117 + image = profile_icon(person, size.to_sym)
  118 + output = {}
  119 +
  120 + unless image.match(/^\/\/www\.gravatar\.com/).nil?
  121 + output[:icon] = 'https:' + image
  122 + else
  123 + output[:icon] = request.url.gsub(/\/api\/.*/, '') + image
  124 + end
  125 +
  126 + present output
  127 + end
111 128 end
112 129  
113 130 resource :profiles do
... ...
app/concerns/authenticated_system.rb
... ... @@ -25,7 +25,19 @@ module AuthenticatedSystem
25 25 # Accesses the current user from the session.
26 26 def current_user user_id = session[:user]
27 27 @current_user ||= begin
28   - user = User.find_by id: user_id if user_id
  28 + user = nil
  29 + if session[:external]
  30 + user = User.new
  31 + external_person = ExternalPerson.find_by(id: session[:external])
  32 + if external_person
  33 + user.external_person_id = external_person.id
  34 + user.email = external_person.email
  35 + else
  36 + session[:external] = nil
  37 + end
  38 + else
  39 + user = User.find_by(id: user_id) if user_id
  40 + end
29 41 user.session = session if user
30 42 User.current = user
31 43 user
... ... @@ -37,9 +49,13 @@ module AuthenticatedSystem
37 49 if new_user.nil?
38 50 session.delete(:user)
39 51 else
40   - session[:user] = new_user.id
  52 + if new_user.id
  53 + session[:user] = new_user.id
  54 + else
  55 + session[:external] = new_user.external_person_id
  56 + end
41 57 new_user.session = session
42   - new_user.register_login
  58 + new_user.register_login if new_user.id
43 59 end
44 60 @current_user = User.current = new_user
45 61 end
... ...
app/controllers/application_controller.rb
... ... @@ -8,6 +8,7 @@ class ApplicationController &lt; ActionController::Base
8 8 before_filter :allow_cross_domain_access
9 9  
10 10 include AuthenticatedSystem
  11 +
11 12 before_filter :require_login_for_environment, :if => :private_environment?
12 13  
13 14 before_filter :verify_members_whitelist, :if => [:private_environment?, :user]
... ... @@ -120,7 +121,9 @@ class ApplicationController &lt; ActionController::Base
120 121 end
121 122  
122 123 def user
123   - current_user.person if logged_in?
  124 + if logged_in?
  125 + current_user.person || current_user.external_person
  126 + end
124 127 end
125 128  
126 129 alias :current_person :user
... ...
app/controllers/my_profile/email_templates_controller.rb
... ... @@ -64,7 +64,7 @@ class EmailTemplatesController &lt; ApplicationController
64 64 private
65 65  
66 66 def template_params
67   - {:profile_name => current_user.name, :environment_name => environment.name }
  67 + {:profile_name => current_person.name, :environment_name => environment.name }
68 68 end
69 69  
70 70 def template_params_allowed params
... ...
app/controllers/public/profile_controller.rb
... ... @@ -176,7 +176,7 @@ class ProfileController &lt; PublicController
176 176 end
177 177  
178 178 def unblock
179   - if current_user.person.is_admin?(profile.environment)
  179 + if current_person.is_admin?(profile.environment)
180 180 profile.unblock
181 181 session[:notice] = _("You have unblocked %s successfully. ") % profile.name
182 182 redirect_to :controller => 'profile', :action => 'index'
... ... @@ -187,7 +187,7 @@ class ProfileController &lt; PublicController
187 187 end
188 188  
189 189 def leave_scrap
190   - sender = params[:sender_id].nil? ? current_user.person : Person.find(params[:sender_id])
  190 + sender = params[:sender_id].nil? ? current_person : Person.find(params[:sender_id])
191 191 receiver = params[:receiver_id].nil? ? @profile : Person.find(params[:receiver_id])
192 192 @scrap = Scrap.new(params[:scrap])
193 193 @scrap.sender= sender
... ... @@ -270,7 +270,7 @@ class ProfileController &lt; PublicController
270 270  
271 271 def remove_scrap
272 272 begin
273   - scrap = current_user.person.scraps(params[:scrap_id])
  273 + scrap = current_person.scraps(params[:scrap_id])
274 274 scrap.destroy
275 275 finish_successful_removal 'Scrap successfully removed.'
276 276 rescue
... ... @@ -395,6 +395,17 @@ class ProfileController &lt; PublicController
395 395 end
396 396 end
397 397  
  398 + def icon
  399 + size = params[:size] || :portrait
  400 + image, mime = profile_icon(profile, size.to_sym, true)
  401 +
  402 + unless image.match(/^\/\/www\.gravatar\.com/).nil?
  403 + redirect_to 'https:' + image
  404 + else
  405 + @file = File.join(Rails.root, 'public', image)
  406 + send_file @file, type: mime, disposition: 'inline'
  407 + end
  408 + end
398 409  
399 410 protected
400 411  
... ...
app/controllers/public/search_controller.rb
... ... @@ -247,7 +247,7 @@ class SearchController &lt; PublicController
247 247 def visible_profiles(klass, *extra_relations)
248 248 relations = [:image, :domains, :environment, :preferred_domain]
249 249 relations += extra_relations
250   - if current_user && current_user.person.is_admin?
  250 + if current_user && current_person.is_admin?
251 251 @environment.send(klass.name.underscore.pluralize).includes(relations)
252 252 else
253 253 @environment.send(klass.name.underscore.pluralize).visible.includes(relations)
... ...
app/helpers/application_helper.rb
... ... @@ -146,12 +146,12 @@ module ApplicationHelper
146 146 end
147 147  
148 148 def link_to_cms(text, profile = nil, options = {})
149   - profile ||= current_user.login
  149 + profile ||= current_person.identifier
150 150 link_to text, myprofile_path(:controller => 'cms', :profile => profile), options
151 151 end
152 152  
153 153 def link_to_profile(text, profile = nil, options = {})
154   - profile ||= current_user.login
  154 + profile ||= current_person.identifier
155 155 link_to text, profile_path(:profile => profile) , options
156 156 end
157 157  
... ... @@ -160,7 +160,7 @@ module ApplicationHelper
160 160 end
161 161  
162 162 def link_if_permitted(link, permission = nil, target = nil)
163   - if permission.nil? || current_user.person.has_permission?(permission, target)
  163 + if permission.nil? || current_person.has_permission?(permission, target)
164 164 link
165 165 else
166 166 nil
... ... @@ -814,7 +814,7 @@ module ApplicationHelper
814 814 {s_('contents|Most commented') => {href: url_for({host: host, controller: 'search', action: 'contents', filter: 'more_comments'})}}
815 815 ]
816 816 if logged_in?
817   - links.push(_('New content') => modal_options({:href => url_for({:controller => 'cms', :action => 'new', :profile => current_user.login, :cms => true})}))
  817 + links.push(_('New content') => modal_options({:href => url_for({:controller => 'cms', :action => 'new', :profile => current_person.identifier, :cms => true})}))
818 818 end
819 819  
820 820 link_to(content_tag(:span, _('Contents'), :class => 'icon-menu-articles'), {:controller => "search", :action => 'contents', :category_path => nil}, :id => 'submenu-contents') +
... ... @@ -830,8 +830,8 @@ module ApplicationHelper
830 830 {s_('people|More popular') => {href: url_for({host: host, controller: 'search', action: 'people', filter: 'more_popular'})}}
831 831 ]
832 832 if logged_in?
833   - links.push(_('My friends') => {:href => url_for({:profile => current_user.login, :controller => 'friends'})})
834   - links.push(_('Invite friends') => {:href => url_for({:profile => current_user.login, :controller => 'invite', :action => 'friends'})})
  833 + links.push(_('My friends') => {:href => url_for({:profile => current_person.identifier, :controller => 'friends'})})
  834 + links.push(_('Invite friends') => {:href => url_for({:profile => current_person.identifier, :controller => 'invite', :action => 'friends'})})
835 835 end
836 836  
837 837 link_to(content_tag(:span, _('People'), :class => 'icon-menu-people'), {:controller => "search", :action => 'people', :category_path => ''}, :id => 'submenu-people') +
... ... @@ -847,8 +847,8 @@ module ApplicationHelper
847 847 {s_('communities|More popular') => {href: url_for({host: host, controller: 'search', action: 'communities', filter: 'more_popular'})}}
848 848 ]
849 849 if logged_in?
850   - links.push(_('My communities') => {:href => url_for({:profile => current_user.login, :controller => 'memberships'})})
851   - links.push(_('New community') => {:href => url_for({:profile => current_user.login, :controller => 'memberships', :action => 'new_community'})})
  850 + links.push(_('My communities') => {:href => url_for({:profile => current_person.identifier, :controller => 'memberships'})})
  851 + links.push(_('New community') => {:href => url_for({:profile => current_person.identifier, :controller => 'memberships', :action => 'new_community'})})
852 852 end
853 853  
854 854 link_to(content_tag(:span, _('Communities'), :class => 'icon-menu-community'), {:controller => "search", :action => 'communities'}, :id => 'submenu-communities') +
... ...
app/helpers/theme_loader_helper.rb
... ... @@ -2,7 +2,7 @@ module ThemeLoaderHelper
2 2 def current_theme
3 3 @current_theme ||=
4 4 begin
5   - if session[:user_theme]
  5 + if defined?(session).present? && session[:user_theme]
6 6 session[:user_theme]
7 7 else
8 8 # utility for developers: set the theme to 'random' in development mode and
... ... @@ -14,7 +14,7 @@ module ThemeLoaderHelper
14 14 elsif Rails.env.development? && respond_to?(:params) && params[:user_theme] && File.exists?(Rails.root.join('public/designs/themes', params[:user_theme]))
15 15 params[:user_theme]
16 16 else
17   - if profile && !profile.theme.nil?
  17 + if defined?(profile) && profile && !profile.theme.nil?
18 18 profile.theme
19 19 elsif environment
20 20 environment.theme
... ... @@ -34,9 +34,9 @@ module ThemeLoaderHelper
34 34 end
35 35  
36 36 def theme_path
37   - if session[:user_theme]
  37 + if defined?(session).present? && session[:user_theme]
38 38 '/user_themes/' + current_theme
39   - elsif session[:theme]
  39 + elsif defined?(session).present? && session[:theme]
40 40 '/designs/themes/' + session[:theme]
41 41 else
42 42 '/designs/themes/' + current_theme
... ...
app/models/abuse_complaint.rb
1 1 class AbuseComplaint < Task
2 2 has_many :abuse_reports, :dependent => :destroy
3   - belongs_to :reported, :class_name => "Profile", :foreign_key => "requestor_id"
  3 + belongs_to :reported, :polymorphic => true, :foreign_key => "requestor_id"
4 4  
5 5 validates_presence_of :reported
6 6 alias :requestor :reported
... ...
app/models/abuse_report.rb
... ... @@ -2,7 +2,7 @@ class AbuseReport &lt; ApplicationRecord
2 2  
3 3 attr_accessible :content, :reason
4 4  
5   - belongs_to :reporter, :class_name => 'Person'
  5 + belongs_to :reporter, :polymorphic => true
6 6 belongs_to :abuse_complaint
7 7 has_many :reported_images, :dependent => :destroy
8 8  
... ...
app/models/comment.rb
... ... @@ -15,7 +15,7 @@ class Comment &lt; ApplicationRecord
15 15 alias :article= :source=
16 16 attr_accessor :follow_article
17 17  
18   - belongs_to :author, :class_name => 'Person', :foreign_key => 'author_id'
  18 + belongs_to :author, :polymorphic => true, :foreign_key => 'author_id'
19 19 has_many :children, :class_name => 'Comment', :foreign_key => 'reply_of_id', :dependent => :destroy
20 20 belongs_to :reply_of, :class_name => 'Comment', :foreign_key => 'reply_of_id'
21 21  
... ...
app/models/concerns/external_user.rb 0 → 100644
... ... @@ -0,0 +1,100 @@
  1 +require 'ostruct'
  2 +
  3 +module ExternalUser
  4 + extend ActiveSupport::Concern
  5 +
  6 + included do
  7 + attr_accessor :external_person_id
  8 + end
  9 +
  10 + def external_person
  11 + ExternalPerson.where(id: self.external_person_id).first
  12 + end
  13 +
  14 + def person_with_external
  15 + self.external_person || self.person_without_external
  16 + end
  17 +
  18 + module ClassMethods
  19 + def webfinger_lookup(login, domain, environment)
  20 + if login && domain && environment.has_federated_network?(domain)
  21 + external_environment = environment.external_environments.find_by_url(domain)
  22 + scheme = "http#{external_environment.uses_ssl? ? 's' : ''}"
  23 + url = URI.parse(scheme+"://"+ domain +'/.well-known/webfinger?resource=acct:'+
  24 + login+'@'+domain)
  25 + http = build_request(url)
  26 + req = Net::HTTP::Get.new(url.to_s)
  27 + res = http.request(req)
  28 + JSON.parse(res.body)
  29 + else
  30 + nil
  31 + end
  32 + end
  33 +
  34 + def build_request(uri)
  35 + request = Net::HTTP.new(uri.host, uri.port)
  36 + if uri.scheme == "https" # enable SSL/TLS
  37 + request.use_ssl = true
  38 + #TODO There may be self-signed certificates that we would not be able
  39 + #to verify, so we'll not verify the ssl certificate for now. Since
  40 + #this requests will go only towards trusted federated networks the admin
  41 + #configured we consider this not to be a big deal. Nonetheless we may be
  42 + #able in the future to require/provide the CA Files on the federation
  43 + #process which would allow us to verify the certificate.
  44 + request.verify_mode = OpenSSL::SSL::VERIFY_NONE
  45 + end
  46 + request
  47 + end
  48 +
  49 + def external_login(login, password, domain)
  50 + # Call Noosfero /api/login
  51 + result = nil
  52 + response = nil
  53 + redirections_allowed = 3
  54 + external_environment = ExternalEnvironment.find_by_url(domain)
  55 + scheme = "http#{external_environment.uses_ssl? ? 's' : ''}"
  56 + location = scheme + '://' + domain + '/api/v1/login'
  57 + request_params = CGI.unescape({ login: login, password: password }.to_query)
  58 + begin
  59 + while redirections_allowed > 0 && (response.blank? || response.code == '301')
  60 + uri = URI.parse(location)
  61 + request = build_request(uri)
  62 + response = request.post(uri.to_s, request_params)
  63 + location = response.header['location']
  64 + redirections_allowed -= 1
  65 + end
  66 + result = response.code.to_i / 100 === 2 ? JSON.parse(response.body) : nil
  67 + rescue
  68 + # Could not make request
  69 + end
  70 + result
  71 + end
  72 +
  73 + # Authenticates a user from an external social network
  74 + def external_authenticate(username, password, environment)
  75 + if username && username.include?('@')
  76 + login, domain = username.split('@')
  77 + webfinger = User.webfinger_lookup(login, domain, environment)
  78 + if webfinger
  79 + user = User.external_login(login, password, domain)
  80 + if user
  81 + u = User.new
  82 + u.email = user['user']['email']
  83 + u.login = login
  84 + webfinger = OpenStruct.new(
  85 + identifier: webfinger['properties']['identifier'],
  86 + name: webfinger['titles']['name'],
  87 + created_at: webfinger['properties']['created_at'],
  88 + domain: domain,
  89 + email: user['user']['email']
  90 + )
  91 + u.external_person_id = ExternalPerson.get_or_create(webfinger).id
  92 + return u
  93 + end
  94 + end
  95 + end
  96 + nil
  97 + end
  98 +
  99 + end
  100 +end
... ...
app/models/concerns/human.rb 0 → 100644
... ... @@ -0,0 +1,42 @@
  1 +module Human
  2 + extend ActiveSupport::Concern
  3 +
  4 + included do
  5 + has_many :comments, :as => :author, :foreign_key => :author_id
  6 + has_many :abuse_reports, :as => :reporter, :foreign_key => 'reporter_id', :dependent => :destroy
  7 +
  8 + scope :abusers, -> {
  9 + joins(:abuse_complaints).where('tasks.status = 3').distinct.select("#{self.table_name}.*")
  10 + }
  11 + scope :non_abusers, -> {
  12 + distinct.select("#{self.table_name}.*").
  13 + joins("LEFT JOIN tasks ON #{self.table_name}.id = tasks.requestor_id AND tasks.type='AbuseComplaint'").
  14 + where("tasks.status != 3 OR tasks.id is NULL")
  15 + }
  16 + end
  17 +
  18 + def already_reported?(profile)
  19 + abuse_reports.any? { |report| report.abuse_complaint.reported == profile && report.abuse_complaint.opened? }
  20 + end
  21 +
  22 + def register_report(abuse_report, profile)
  23 + AbuseComplaint.create!(:reported => profile, :target => profile.environment) if !profile.opened_abuse_complaint
  24 + abuse_report.abuse_complaint = profile.opened_abuse_complaint
  25 + abuse_report.reporter = self
  26 + abuse_report.save!
  27 + end
  28 +
  29 + def abuser?
  30 + AbuseComplaint.finished.where(:requestor_id => self).count > 0
  31 + end
  32 +
  33 + # Sets the identifier for this person. Raises an exception when called on a
  34 + # existing person (since peoples' identifiers cannot be changed)
  35 + def identifier=(value)
  36 + unless self.new_record?
  37 + raise ArgumentError.new(_('An existing person cannot be renamed.'))
  38 + end
  39 + self[:identifier] = value
  40 + end
  41 +
  42 +end
... ...
app/models/concerns/profile_entity.rb 0 → 100644
... ... @@ -0,0 +1,155 @@
  1 +module ProfileEntity
  2 + extend ActiveSupport::Concern
  3 +
  4 + included do
  5 + attr_accessible :name, :identifier, :environment
  6 +
  7 + validates_presence_of :identifier, :name
  8 +
  9 + belongs_to :environment
  10 + has_many :search_terms, :as => :context
  11 + has_many :abuse_complaints, :as => :reported, :foreign_key => 'requestor_id', :dependent => :destroy
  12 +
  13 + before_create :set_default_environment
  14 +
  15 + scope :recent, -> limit=nil { order('id DESC').limit(limit) }
  16 +
  17 + end
  18 +
  19 + def disable
  20 + self.visible = false
  21 + self.save
  22 + end
  23 +
  24 + def enable
  25 + self.visible = true
  26 + self.save
  27 + end
  28 +
  29 + def opened_abuse_complaint
  30 + abuse_complaints.opened.first
  31 + end
  32 +
  33 + def set_default_environment
  34 + if self.environment.nil?
  35 + self.environment = Environment.default
  36 + end
  37 + true
  38 + end
  39 +
  40 + # returns +false+
  41 + def person?
  42 + self.kind_of?(Person)
  43 + end
  44 +
  45 + def enterprise?
  46 + self.kind_of?(Enterprise)
  47 + end
  48 +
  49 + def organization?
  50 + self.kind_of?(Organization)
  51 + end
  52 +
  53 + def community?
  54 + self.kind_of?(Community)
  55 + end
  56 +
  57 + include ActionView::Helpers::TextHelper
  58 + def short_name(chars = 40)
  59 + if self[:nickname].blank?
  60 + if chars
  61 + truncate self.name, length: chars, omission: '...'
  62 + else
  63 + self.name
  64 + end
  65 + else
  66 + self[:nickname]
  67 + end
  68 + end
  69 +
  70 + def to_liquid
  71 + HashWithIndifferentAccess.new :name => name, :identifier => identifier
  72 + end
  73 +
  74 + # Tells whether a specified profile has members or nor.
  75 + #
  76 + # On this class, returns <tt>false</tt> by default.
  77 + def has_members?
  78 + false
  79 + end
  80 +
  81 + def apply_type_specific_template(template)
  82 + end
  83 +
  84 + # Override this method in subclasses of Profile to create a default article
  85 + # set upon creation. Note that this method will be called *only* if there is
  86 + # no template for the type of profile (i.e. if the template was removed or in
  87 + # the creation of the template itself).
  88 + #
  89 + # This method must return an array of pre-populated articles, which will be
  90 + # associated to the profile before being saved. Example:
  91 + #
  92 + # def default_set_of_articles
  93 + # [Blog.new(:name => 'Blog'), Gallery.new(:name => 'Gallery')]
  94 + # end
  95 + #
  96 + # By default, this method returns an empty array.
  97 + def default_set_of_articles
  98 + []
  99 + end
  100 +
  101 + def blocks_to_expire_cache
  102 + []
  103 + end
  104 +
  105 + def cache_keys(params = {})
  106 + []
  107 + end
  108 +
  109 + def members_cache_key(params = {})
  110 + page = params[:npage] || '1'
  111 + sort = (params[:sort] == 'desc') ? params[:sort] : 'asc'
  112 + cache_key + '-members-page-' + page + '-' + sort
  113 + end
  114 +
  115 + def more_recent_label
  116 + _("Since: ")
  117 + end
  118 +
  119 + def control_panel_settings_button
  120 + {:title => _('Edit Profile'), :icon => 'edit-profile'}
  121 + end
  122 +
  123 + def control_panel_settings_button
  124 + {:title => _('Profile Info and settings'), :icon => 'edit-profile'}
  125 + end
  126 +
  127 + def exclude_verbs_on_activities
  128 + %w[]
  129 + end
  130 +
  131 + def allow_invitation_from(person)
  132 + false
  133 + end
  134 +
  135 + def allow_post_content?(person = nil)
  136 + person.kind_of?(Profile) && person.has_permission?('post_content', self)
  137 + end
  138 +
  139 + def allow_edit?(person = nil)
  140 + person.kind_of?(Profile) && person.has_permission?('edit_profile', self)
  141 + end
  142 +
  143 + def allow_destroy?(person = nil)
  144 + person.kind_of?(Profile) && person.has_permission?('destroy_profile', self)
  145 + end
  146 +
  147 + module ClassMethods
  148 +
  149 + def identification
  150 + name
  151 + end
  152 +
  153 + end
  154 +
  155 +end
... ...
app/models/environment.rb
... ... @@ -1009,6 +1009,10 @@ class Environment &lt; ApplicationRecord
1009 1009 HashWithIndifferentAccess.new :name => name
1010 1010 end
1011 1011  
  1012 + def has_federated_network?(domain)
  1013 + self.external_environments.map(&:url).any? { |url| /http[s]?:\/\/#{domain}\/?/ =~ url }
  1014 + end
  1015 +
1012 1016 private
1013 1017  
1014 1018 def default_language_available
... ...
app/models/external_environment.rb
... ... @@ -6,4 +6,9 @@ class ExternalEnvironment &lt; ActiveRecord::Base
6 6  
7 7 has_many :environment_external_environments, dependent: :destroy
8 8 has_many :environments, through: :environment_external_environments
  9 +
  10 + def uses_ssl?
  11 + url.starts_with? 'https'
  12 + end
  13 +
9 14 end
... ...
app/models/external_person.rb 0 → 100644
... ... @@ -0,0 +1,290 @@
  1 +# A pseudo profile is a person from a remote network
  2 +class ExternalPerson < ActiveRecord::Base
  3 +
  4 + include Human
  5 + include ProfileEntity
  6 +
  7 + validates_uniqueness_of :identifier, scope: :source
  8 +
  9 + validates_presence_of :source, :email, :created_at
  10 +
  11 + attr_accessible :source, :email, :created_at
  12 +
  13 + def self.get_or_create(webfinger)
  14 + user = ExternalPerson.find_by(identifier: webfinger.identifier, source: webfinger.domain)
  15 + if user.nil?
  16 + user = ExternalPerson.create!(identifier: webfinger.identifier,
  17 + name: webfinger.name,
  18 + source: webfinger.domain,
  19 + email: webfinger.email,
  20 + created_at: webfinger.created_at
  21 + )
  22 + end
  23 + user
  24 + end
  25 +
  26 + def privacy_setting
  27 + _('Public profile')
  28 + end
  29 +
  30 + def avatar
  31 + "http://#{self.source}/profile/#{self.identifier}/icon/"
  32 + end
  33 +
  34 + def url
  35 + "http://#{self.source}/profile/#{self.identifier}"
  36 + end
  37 +
  38 + alias :public_profile_url :url
  39 +
  40 + def admin_url
  41 + "http://#{self.source}/myprofile/#{self.identifier}"
  42 + end
  43 +
  44 + def wall_url
  45 + self.url
  46 + end
  47 + def tasks_url
  48 + self.url
  49 + end
  50 + def leave_url(reload = false)
  51 + self.url
  52 + end
  53 + def join_url
  54 + self.url
  55 + end
  56 + def join_not_logged_url
  57 + self.url
  58 + end
  59 + def check_membership_url
  60 + self.url
  61 + end
  62 + def add_url
  63 + self.url
  64 + end
  65 + def check_friendship_url
  66 + self.url
  67 + end
  68 + def people_suggestions_url
  69 + self.url
  70 + end
  71 + def communities_suggestions_url
  72 + self.url
  73 + end
  74 + def top_url(scheme = 'http')
  75 + "#{scheme}://#{self.source}"
  76 + end
  77 +
  78 + def profile_custom_icon(gravatar_default=nil)
  79 + self.avatar
  80 + end
  81 +
  82 + def preferred_login_redirection
  83 + environment.redirection_after_login
  84 + end
  85 +
  86 + def location
  87 + self.source
  88 + end
  89 +
  90 + def default_hostname
  91 + environment.default_hostname
  92 + end
  93 +
  94 + def possible_domains
  95 + environment.domains
  96 + end
  97 +
  98 + def person?
  99 + true
  100 + end
  101 +
  102 + def contact_email(*args)
  103 + self.email
  104 + end
  105 +
  106 + def notification_emails
  107 + [self.contact_email]
  108 + end
  109 +
  110 + def email_domain
  111 + self.source
  112 + end
  113 +
  114 + def email_addresses
  115 + ['%s@%s' % [self.identifier, self.source] ]
  116 + end
  117 +
  118 + def jid(options = {})
  119 + "#{self.identifier}@#{self.source}"
  120 + end
  121 + def full_jid(options = {})
  122 + "#{jid(options)}/#{self.name}"
  123 + end
  124 +
  125 + class ExternalPerson::Image
  126 + def initialize(path)
  127 + @path = path
  128 + end
  129 +
  130 + def public_filename(size = nil)
  131 + URI.join(@path, size.to_s)
  132 + end
  133 +
  134 + def content_type
  135 + # This is not really going to be used anywhere that matters
  136 + # so we are hardcodding it here.
  137 + 'image/png'
  138 + end
  139 + end
  140 +
  141 + def image
  142 + ExternalPerson::Image.new(avatar)
  143 + end
  144 +
  145 + def data_hash(gravatar_default = nil)
  146 + friends_list = {}
  147 + {
  148 + 'login' => self.identifier,
  149 + 'name' => self.name,
  150 + 'email' => self.email,
  151 + 'avatar' => self.profile_custom_icon(gravatar_default),
  152 + 'is_admin' => self.is_admin?,
  153 + 'since_month' => self.created_at.month,
  154 + 'since_year' => self.created_at.year,
  155 + 'email_domain' => self.source,
  156 + 'friends_list' => friends_list,
  157 + 'enterprises' => [],
  158 + 'amount_of_friends' => friends_list.count,
  159 + 'chat_enabled' => false
  160 + }
  161 + end
  162 +
  163 + # External Person should respond to all methods in Person and Profile
  164 + def person_instance_methods
  165 + methods_and_responses = {
  166 + enterprises: Enterprise.none, communities: Community.none, friends:
  167 + Person.none, memberships: Profile.none, friendships: Person.none,
  168 + following_articles: Article.none, article_followers: ArticleFollower.none,
  169 + requested_tasks: Task.none, mailings: Mailing.none, scraps_sent:
  170 + Scrap.none, favorite_enterprise_people: FavoriteEnterprisePerson.none,
  171 + favorite_enterprises: Enterprise.none, acepted_forums: Forum.none,
  172 + articles_with_access: Article.none, suggested_profiles:
  173 + ProfileSuggestion.none, suggested_people: ProfileSuggestion.none,
  174 + suggested_communities: ProfileSuggestion.none, user: nil,
  175 + refused_communities: Community.none, has_permission?: false,
  176 + has_permission_with_admin?: false, has_permission_without_admin?: false,
  177 + has_permission_with_plugins?: false, has_permission_without_plugins?:
  178 + false, memberships_by_role: Person.none, can_change_homepage?: false,
  179 + can_control_scrap?: false, receives_scrap_notification?: false,
  180 + can_control_activity?: false, can_post_content?: false,
  181 + suggested_friend_groups: [], friend_groups: [], add_friend: nil,
  182 + already_request_friendship?: false, remove_friend: nil,
  183 + presence_of_required_fields: nil, active_fields: [], required_fields: [],
  184 + signup_fields: [], default_set_of_blocks: [], default_set_of_boxes: [],
  185 + default_set_of_articles: [], cell_phone: nil, comercial_phone: nil,
  186 + nationality: nil, schooling: nil, contact_information: nil, sex: nil,
  187 + birth_date: nil, jabber_id: nil, personal_website: nil, address_reference:
  188 + nil, district: nil, schooling_status: nil, formation: nil,
  189 + custom_formation: nil, area_of_study: nil, custom_area_of_study: nil,
  190 + professional_activity: nil, organization_website: nil, organization: nil,
  191 + photo: nil, city: nil, state: nil, country: nil, zip_code: nil,
  192 + address_line2: nil, copy_communities_from: nil,
  193 + has_organization_pending_tasks?: false, organizations_with_pending_tasks:
  194 + Organization.none, pending_tasks_for_organization: Task.none,
  195 + build_contact: nil, is_a_friend?: false, ask_to_join?: false, refuse_join:
  196 + nil, blocks_to_expire_cache: [], cache_keys: [], communities_cache_key: '',
  197 + friends_cache_key: '', manage_friends_cache_key: '',
  198 + relationships_cache_key: '', is_member_of?: false, follows?: false,
  199 + each_friend: nil, is_last_admin?: false, is_last_admin_leaving?: false,
  200 + leave: nil, last_notification: nil, notification_time: 0, notifier: nil,
  201 + remove_suggestion: nil, allow_invitation_from?: false
  202 + }
  203 +
  204 + derivated_methods = generate_derivated_methods(methods_and_responses)
  205 + derivated_methods.merge(methods_and_responses)
  206 + end
  207 +
  208 + def profile_instance_methods
  209 + methods_and_responses = {
  210 + role_assignments: RoleAssignment.none, favorite_enterprises:
  211 + Enterprise.none, memberships: Profile.none, friendships: Profile.none,
  212 + tasks: Task.none, suggested_profiles: ProfileSuggestion.none,
  213 + suggested_people: ProfileSuggestion.none, suggested_communities:
  214 + ProfileSuggestion.none, public_profile: true, nickname: nil, custom_footer:
  215 + '', custom_header: '', address: '', zip_code: '', contact_phone: '',
  216 + image_builder: nil, description: '', closed: false, template_id: nil, lat:
  217 + nil, lng: nil, is_template: false, fields_privacy: {}, preferred_domain_id:
  218 + nil, category_ids: [], country: '', city: '', state: '',
  219 + national_region_code: '', redirect_l10n: false, notification_time: 0,
  220 + custom_url_redirection: nil, email_suggestions: false,
  221 + allow_members_to_invite: false, invite_friends_only: false, secret: false,
  222 + profile_admin_mail_notification: false, redirection_after_login: nil,
  223 + profile_activities: ProfileActivity.none, action_tracker_notifications:
  224 + ActionTrackerNotification.none, tracked_notifications:
  225 + ActionTracker::Record.none, scraps_received: Scrap.none, template:
  226 + Profile.none, comments_received: Comment.none, email_templates:
  227 + EmailTemplate.none, members: Profile.none, members_like: Profile.none,
  228 + members_by: Profile.none, members_by_role: Profile.none, scraps:
  229 + Scrap.none, welcome_page_content: nil, settings: {}, find_in_all_tasks:
  230 + nil, top_level_categorization: {}, interests: Category.none, geolocation:
  231 + '', country_name: '', pending_categorizations: [], add_category: false,
  232 + create_pending_categorizations: false, top_level_articles: Article.none,
  233 + valid_identifier: true, valid_template: false, create_default_set_of_boxes:
  234 + true, copy_blocks_from: nil, default_template: nil,
  235 + template_without_default: nil, template_with_default: nil, apply_template:
  236 + false, iframe_whitelist: [], recent_documents: Article.none, last_articles:
  237 + Article.none, is_validation_entity?: false, hostname: nil, own_hostname:
  238 + nil, article_tags: {}, tagged_with: Article.none,
  239 + insert_default_article_set: false, copy_articles_from: true,
  240 + copy_article_tree: nil, copy_article?: false, add_member: false,
  241 + remove_member: false, add_admin: false, remove_admin: false, add_moderator:
  242 + false, display_info_to?: true, update_category_from_region: nil,
  243 + accept_category?: false, custom_header_expanded: '',
  244 + custom_footer_expanded: '', public?: true, themes: [], find_theme: nil,
  245 + blogs: Blog.none, blog: nil, has_blog?: false, forums: Forum.none, forum:
  246 + nil, has_forum?: false, admins: [], settings_field: {}, setting_changed:
  247 + false, public_content: true, enable_contact?: false, folder_types: [],
  248 + folders: Article.none, image_galleries: Article.none, image_valid: true,
  249 + update_header_and_footer: nil, update_theme: nil, update_layout_template:
  250 + nil, recent_actions: ActionTracker::Record.none, recent_notifications:
  251 + ActionTracker::Record.none, more_active_label: _('no activity'),
  252 + more_popular_label: _('no members'), profile_custom_image: nil,
  253 + is_on_homepage?: false, activities: ProfileActivity.none,
  254 + may_display_field_to?: true, may_display_location_to?: true, public_fields:
  255 + {}, followed_by?: false, display_private_info_to?: true, can_view_field?:
  256 + true, remove_from_suggestion_list: nil, layout_template: 'default',
  257 + is_admin?: false, add_friend: false, follows?: false, is_a_friend?: false,
  258 + already_request_friendship?: false
  259 + }
  260 +
  261 + derivated_methods = generate_derivated_methods(methods_and_responses)
  262 + derivated_methods.merge(methods_and_responses)
  263 + end
  264 +
  265 + def method_missing(method, *args, &block)
  266 + if person_instance_methods.keys.include?(method)
  267 + return person_instance_methods[method]
  268 + end
  269 + if profile_instance_methods.keys.include? method
  270 + return profile_instance_methods[method]
  271 + end
  272 + end
  273 +
  274 + def respond_to_missing?(method_name, include_private = false)
  275 + person_instance_methods.keys.include?(method_name) ||
  276 + profile_instance_methods.keys.include?(method_name) ||
  277 + super
  278 + end
  279 +
  280 + private
  281 +
  282 + def generate_derivated_methods(methods)
  283 + derivated_methods = {}
  284 + methods.keys.each do |method|
  285 + derivated_methods[method.to_s.insert(-1, '?').to_sym] = false
  286 + derivated_methods[method.to_s.insert(-1, '=').to_sym] = nil
  287 + end
  288 + derivated_methods
  289 + end
  290 +end
... ...
app/models/person.rb
1 1 # A person is the profile of an user holding all relationships with the rest of the system
2 2 class Person < Profile
3 3  
  4 + include Human
  5 +
4 6 attr_accessible :organization, :contact_information, :sex, :birth_date, :cell_phone, :comercial_phone, :jabber_id, :personal_website, :nationality, :address_reference, :district, :schooling, :schooling_status, :formation, :custom_formation, :area_of_study, :custom_area_of_study, :professional_activity, :organization_website, :following_articles
5 7  
6 8 SEARCH_FILTERS = {
... ... @@ -88,7 +90,6 @@ class Person &lt; Profile
88 90 memberships.where('role_assignments.role_id = ?', role.id)
89 91 end
90 92  
91   - has_many :comments, :foreign_key => :author_id
92 93 has_many :article_followers, :dependent => :destroy
93 94 has_many :following_articles, :class_name => 'Article', :through => :article_followers, :source => :article
94 95 has_many :friendships, :dependent => :destroy
... ... @@ -100,8 +101,6 @@ class Person &lt; Profile
100 101  
101 102 has_many :requested_tasks, :class_name => 'Task', :foreign_key => :requestor_id, :dependent => :destroy
102 103  
103   - has_many :abuse_reports, :foreign_key => 'reporter_id', :dependent => :destroy
104   -
105 104 has_many :mailings
106 105  
107 106 has_many :scraps_sent, :class_name => 'Scrap', :foreign_key => :sender_id, :dependent => :destroy
... ... @@ -123,15 +122,6 @@ class Person &lt; Profile
123 122  
124 123 scope :more_popular, -> { order 'friends_count DESC' }
125 124  
126   - scope :abusers, -> {
127   - joins(:abuse_complaints).where('tasks.status = 3').distinct.select('profiles.*')
128   - }
129   - scope :non_abusers, -> {
130   - distinct.select("profiles.*").
131   - joins("LEFT JOIN tasks ON profiles.id = tasks.requestor_id AND tasks.type='AbuseComplaint'").
132   - where("tasks.status != 3 OR tasks.id is NULL")
133   - }
134   -
135 125 scope :admins, -> { joins(:role_assignments => :role).where('roles.key = ?', 'environment_administrator') }
136 126 scope :activated, -> { joins(:user).where('users.activation_code IS NULL AND users.activated_at IS NOT NULL') }
137 127 scope :deactivated, -> { joins(:user).where('NOT (users.activation_code IS NULL AND users.activated_at IS NOT NULL)') }
... ... @@ -174,15 +164,6 @@ class Person &lt; Profile
174 164 (self.has_permission?('post_content', profile) || self.has_permission?('publish_content', profile))
175 165 end
176 166  
177   - # Sets the identifier for this person. Raises an exception when called on a
178   - # existing person (since peoples' identifiers cannot be changed)
179   - def identifier=(value)
180   - unless self.new_record?
181   - raise ArgumentError.new(_('An existing person cannot be renamed.'))
182   - end
183   - self[:identifier] = value
184   - end
185   -
186 167 def suggested_friend_groups
187 168 (friend_groups.compact + [ _('friends'), _('work'), _('school'), _('family') ]).map {|i| i if !i.empty?}.compact.uniq
188 169 end
... ... @@ -192,7 +173,7 @@ class Person &lt; Profile
192 173 end
193 174  
194 175 def add_friend(friend, group = nil)
195   - unless self.is_a_friend?(friend)
  176 + unless self.is_a_friend?(friend) || friend.is_a?(ExternalPerson)
196 177 friendship = self.friendships.build
197 178 friendship.friend = friend
198 179 friendship.group = group
... ... @@ -517,21 +498,6 @@ class Person &lt; Profile
517 498 leave_hash.to_json
518 499 end
519 500  
520   - def already_reported?(profile)
521   - abuse_reports.any? { |report| report.abuse_complaint.reported == profile && report.abuse_complaint.opened? }
522   - end
523   -
524   - def register_report(abuse_report, profile)
525   - AbuseComplaint.create!(:reported => profile, :target => profile.environment) if !profile.opened_abuse_complaint
526   - abuse_report.abuse_complaint = profile.opened_abuse_complaint
527   - abuse_report.reporter = self
528   - abuse_report.save!
529   - end
530   -
531   - def abuser?
532   - AbuseComplaint.finished.where(:requestor_id => self).count > 0
533   - end
534   -
535 501 def control_panel_settings_button
536 502 {:title => _('Edit Profile'), :icon => 'edit-profile'}
537 503 end
... ... @@ -580,6 +546,34 @@ class Person &lt; Profile
580 546 person.has_permission?(:manage_friends, self)
581 547 end
582 548  
  549 + def data_hash(gravatar_default = nil)
  550 + friends_list = {}
  551 + enterprises = self.enterprises.map { |e| { 'name' => e.short_name, 'identifier' => e.identifier } }
  552 + self.friends.online.map do |person|
  553 + friends_list[person.identifier] = {
  554 + 'avatar' => person.profile_custom_icon(gravatar_default),
  555 + 'name' => person.short_name,
  556 + 'jid' => person.full_jid,
  557 + 'status' => person.user.chat_status,
  558 + }
  559 + end
  560 +
  561 + {
  562 + 'login' => self.identifier,
  563 + 'name' => self.name,
  564 + 'email' => self.email,
  565 + 'avatar' => self.profile_custom_icon(gravatar_default),
  566 + 'is_admin' => self.is_admin?,
  567 + 'since_month' => self.created_at.month,
  568 + 'since_year' => self.created_at.year,
  569 + 'email_domain' => self.user.enable_email ? self.user.email_domain : nil,
  570 + 'friends_list' => friends_list,
  571 + 'enterprises' => enterprises,
  572 + 'amount_of_friends' => friends_list.count,
  573 + 'chat_enabled' => self.environment.enabled?('xmpp_chat')
  574 + }
  575 + end
  576 +
583 577 protected
584 578  
585 579 def followed_by?(profile)
... ...
app/models/profile.rb
... ... @@ -3,9 +3,10 @@
3 3 # which by default is the one returned by Environment:default.
4 4 class Profile < ApplicationRecord
5 5  
6   - attr_accessible :name, :identifier, :public_profile, :nickname, :custom_footer, :custom_header, :address, :zip_code, :contact_phone, :image_builder, :description, :closed, :template_id, :environment, :lat, :lng, :is_template, :fields_privacy, :preferred_domain_id, :category_ids, :country, :city, :state, :national_region_code, :email, :contact_email, :redirect_l10n, :notification_time,
7   - :redirection_after_login, :custom_url_redirection,
8   - :email_suggestions, :allow_members_to_invite, :invite_friends_only, :secret, :profile_admin_mail_notification
  6 + include ProfileEntity
  7 +
  8 + attr_accessible :public_profile, :nickname, :custom_footer, :custom_header, :address, :zip_code, :contact_phone, :image_builder, :description, :closed, :template_id, :lat, :lng, :is_template, :fields_privacy, :preferred_domain_id, :category_ids, :country, :city, :state, :national_region_code, :email, :contact_email, :redirect_l10n, :notification_time,
  9 + :custom_url_redirection, :email_suggestions, :allow_members_to_invite, :invite_friends_only, :secret, :profile_admin_mail_notification, :redirection_after_login
9 10  
10 11 # use for internationalizable human type names in search facets
11 12 # reimplement on subclasses
... ... @@ -115,8 +116,6 @@ class Profile &lt; ApplicationRecord
115 116 }
116 117 scope :no_templates, -> { where is_template: false }
117 118  
118   - scope :recent, -> limit=nil { order('id DESC').limit(limit) }
119   -
120 119  
121 120 # Returns a scoped object to select profiles in a given location or in a radius
122 121 # distance from the given location center.
... ... @@ -224,8 +223,6 @@ class Profile &lt; ApplicationRecord
224 223 welcome_page && welcome_page.published ? welcome_page.body : nil
225 224 end
226 225  
227   - has_many :search_terms, :as => :context
228   -
229 226 def scraps(scrap=nil)
230 227 scrap = scrap.is_a?(Scrap) ? scrap.id : scrap
231 228 scrap.nil? ? Scrap.all_scraps(self) : Scrap.all_scraps(self).find(scrap)
... ... @@ -278,7 +275,6 @@ class Profile &lt; ApplicationRecord
278 275  
279 276 has_many :domains, :as => :owner
280 277 belongs_to :preferred_domain, :class_name => 'Domain', :foreign_key => 'preferred_domain_id'
281   - belongs_to :environment
282 278  
283 279 has_many :articles, :dependent => :destroy
284 280 belongs_to :home_page, :class_name => Article.name, :foreign_key => 'home_page_id'
... ... @@ -305,8 +301,6 @@ class Profile &lt; ApplicationRecord
305 301 has_many :profile_categorizations_including_virtual, :class_name => 'ProfileCategorization'
306 302 has_many :categories_including_virtual, :through => :profile_categorizations_including_virtual, :source => :category
307 303  
308   - has_many :abuse_complaints, :foreign_key => 'requestor_id', :dependent => :destroy
309   -
310 304 has_many :profile_suggestions, :foreign_key => :suggestion_id, :dependent => :destroy
311 305  
312 306 def top_level_categorization
... ... @@ -401,7 +395,6 @@ class Profile &lt; ApplicationRecord
401 395 self.all
402 396 end
403 397  
404   - validates_presence_of :identifier, :name
405 398 validates_length_of :nickname, :maximum => 16, :allow_nil => true
406 399 validate :valid_template
407 400 validate :valid_identifier
... ... @@ -416,14 +409,6 @@ class Profile &lt; ApplicationRecord
416 409 end
417 410 end
418 411  
419   - before_create :set_default_environment
420   - def set_default_environment
421   - if self.environment.nil?
422   - self.environment = Environment.default
423   - end
424   - true
425   - end
426   -
427 412 # registar callback for creating boxes after the object is created.
428 413 after_create :create_default_set_of_boxes
429 414  
... ... @@ -496,9 +481,6 @@ class Profile &lt; ApplicationRecord
496 481 self.save(:validate => false)
497 482 end
498 483  
499   - def apply_type_specific_template(template)
500   - end
501   -
502 484 xss_terminate :only => [ :name, :nickname, :address, :contact_phone, :description ], :on => 'validation'
503 485 xss_terminate :only => [ :custom_footer, :custom_header ], :with => 'white_list'
504 486  
... ... @@ -539,10 +521,6 @@ class Profile &lt; ApplicationRecord
539 521 ).order('articles.published_at desc, articles.id desc')
540 522 end
541 523  
542   - def to_liquid
543   - HashWithIndifferentAccess.new :name => name, :identifier => identifier
544   - end
545   -
546 524 class << self
547 525  
548 526 # finds a profile by its identifier. This method is a shortcut to
... ... @@ -562,23 +540,6 @@ class Profile &lt; ApplicationRecord
562 540 environment
563 541 end
564 542  
565   - # returns +false+
566   - def person?
567   - self.kind_of?(Person)
568   - end
569   -
570   - def enterprise?
571   - self.kind_of?(Enterprise)
572   - end
573   -
574   - def organization?
575   - self.kind_of?(Organization)
576   - end
577   -
578   - def community?
579   - self.kind_of?(Community)
580   - end
581   -
582 543 # returns false.
583 544 def is_validation_entity?
584 545 false
... ... @@ -683,13 +644,6 @@ private :generate_url, :url_options
683 644 self.articles.tagged_with(tag)
684 645 end
685 646  
686   - # Tells whether a specified profile has members or nor.
687   - #
688   - # On this class, returns <tt>false</tt> by default.
689   - def has_members?
690   - false
691   - end
692   -
693 647 after_create :insert_default_article_set
694 648 def insert_default_article_set
695 649 if template
... ... @@ -704,23 +658,6 @@ private :generate_url, :url_options
704 658 end
705 659 end
706 660  
707   - # Override this method in subclasses of Profile to create a default article
708   - # set upon creation. Note that this method will be called *only* if there is
709   - # no template for the type of profile (i.e. if the template was removed or in
710   - # the creation of the template itself).
711   - #
712   - # This method must return an array of pre-populated articles, which will be
713   - # associated to the profile before being saved. Example:
714   - #
715   - # def default_set_of_articles
716   - # [Blog.new(:name => 'Blog'), Gallery.new(:name => 'Gallery')]
717   - # end
718   - #
719   - # By default, this method returns an empty array.
720   - def default_set_of_articles
721   - []
722   - end
723   -
724 661 def copy_articles_from other
725 662 return false if other.top_level_articles.empty?
726 663 other.top_level_articles.each do |a|
... ... @@ -816,19 +753,6 @@ private :generate_url, :url_options
816 753 !forbidden.include?(cat.class)
817 754 end
818 755  
819   - include ActionView::Helpers::TextHelper
820   - def short_name(chars = 40)
821   - if self[:nickname].blank?
822   - if chars
823   - truncate self.name, length: chars, omission: '...'
824   - else
825   - self.name
826   - end
827   - else
828   - self[:nickname]
829   - end
830   - end
831   -
832 756 def custom_header
833 757 self[:custom_header] || environment && environment.custom_header
834 758 end
... ... @@ -934,14 +858,6 @@ private :generate_url, :url_options
934 858 articles.galleries
935 859 end
936 860  
937   - def blocks_to_expire_cache
938   - []
939   - end
940   -
941   - def cache_keys(params = {})
942   - []
943   - end
944   -
945 861 validate :image_valid
946 862  
947 863 def image_valid
... ... @@ -978,16 +894,6 @@ private :generate_url, :url_options
978 894 self.update_attribute(:layout_template, template)
979 895 end
980 896  
981   - def members_cache_key(params = {})
982   - page = params[:npage] || '1'
983   - sort = (params[:sort] == 'desc') ? params[:sort] : 'asc'
984   - cache_key + '-members-page-' + page + '-' + sort
985   - end
986   -
987   - def more_recent_label
988   - _("Since: ")
989   - end
990   -
991 897 def recent_actions
992 898 tracked_actions.recent
993 899 end
... ... @@ -1042,32 +948,6 @@ private :generate_url, :url_options
1042 948 end
1043 949 end
1044 950  
1045   - def opened_abuse_complaint
1046   - abuse_complaints.opened.first
1047   - end
1048   -
1049   - def disable
1050   - self.visible = false
1051   - self.save
1052   - end
1053   -
1054   - def enable
1055   - self.visible = true
1056   - self.save
1057   - end
1058   -
1059   - def control_panel_settings_button
1060   - {:title => _('Edit Profile'), :icon => 'edit-profile'}
1061   - end
1062   -
1063   - def self.identification
1064   - name
1065   - end
1066   -
1067   - def exclude_verbs_on_activities
1068   - %w[]
1069   - end
1070   -
1071 951 # Customize in subclasses
1072 952 def activities
1073 953 self.profile_activities.includes(:activity).order('updated_at DESC')
... ... @@ -1102,10 +982,6 @@ private :generate_url, :url_options
1102 982 self.active_fields
1103 983 end
1104 984  
1105   - def control_panel_settings_button
1106   - {:title => _('Profile Info and settings'), :icon => 'edit-profile'}
1107   - end
1108   -
1109 985 def followed_by?(person)
1110 986 person.is_member_of?(self)
1111 987 end
... ... @@ -1133,19 +1009,4 @@ private :generate_url, :url_options
1133 1009 suggestion.disable if suggestion
1134 1010 end
1135 1011  
1136   - def allow_invitation_from(person)
1137   - false
1138   - end
1139   -
1140   - def allow_post_content?(person = nil)
1141   - person.kind_of?(Profile) && person.has_permission?('post_content', self)
1142   - end
1143   -
1144   - def allow_edit?(person = nil)
1145   - person.kind_of?(Profile) && person.has_permission?('edit_profile', self)
1146   - end
1147   -
1148   - def allow_destroy?(person = nil)
1149   - person.kind_of?(Profile) && person.has_permission?('destroy_profile', self)
1150   - end
1151 1012 end
... ...
app/models/user.rb
... ... @@ -7,6 +7,8 @@ class User &lt; ApplicationRecord
7 7  
8 8 attr_accessible :login, :email, :password, :password_confirmation, :activated_at
9 9  
  10 + include ExternalUser
  11 +
10 12 N_('Password')
11 13 N_('Password confirmation')
12 14 N_('Terms accepted')
... ... @@ -105,6 +107,8 @@ class User &lt; ApplicationRecord
105 107 has_one :person, dependent: :destroy, autosave: false
106 108 belongs_to :environment
107 109  
  110 + alias_method_chain :person, :external
  111 +
108 112 has_many :sessions, dependent: :destroy
109 113 # holds the current session, see lib/authenticated_system.rb
110 114 attr_accessor :session
... ... @@ -146,7 +150,8 @@ class User &lt; ApplicationRecord
146 150 u.generate_private_token_if_not_exist
147 151 return u
148 152 end
149   - return nil
  153 +
  154 + return User.external_authenticate(login, password, environment)
150 155 end
151 156  
152 157 def register_login
... ... @@ -380,31 +385,7 @@ class User &lt; ApplicationRecord
380 385 end
381 386  
382 387 def data_hash(gravatar_default = nil)
383   - friends_list = {}
384   - enterprises = person.enterprises.map { |e| { 'name' => e.short_name, 'identifier' => e.identifier } }
385   - self.person.friends.online.map do |person|
386   - friends_list[person.identifier] = {
387   - 'avatar' => person.profile_custom_icon(gravatar_default),
388   - 'name' => person.short_name,
389   - 'jid' => person.full_jid,
390   - 'status' => person.user.chat_status,
391   - }
392   - end
393   -
394   - {
395   - 'login' => self.login,
396   - 'name' => self.person.name,
397   - 'email' => self.email,
398   - 'avatar' => self.person.profile_custom_icon(gravatar_default),
399   - 'is_admin' => self.person.is_admin?,
400   - 'since_month' => self.person.created_at.month,
401   - 'since_year' => self.person.created_at.year,
402   - 'email_domain' => self.enable_email ? self.email_domain : nil,
403   - 'friends_list' => friends_list,
404   - 'enterprises' => enterprises,
405   - 'amount_of_friends' => friends_list.count,
406   - 'chat_enabled' => person.environment.enabled?('xmpp_chat')
407   - }
  388 + self.person.data_hash(gravatar_default)
408 389 end
409 390  
410 391 def self.expires_chat_status_every
... ...
app/views/account/welcome.html.erb
... ... @@ -4,7 +4,7 @@
4 4 <%= _('%s was successfuly activated. Now you may go to your control panel or to the control panel of your enterprise') % @enterprise.name %>
5 5  
6 6 <%= button_bar do %>
7   - <%= button 'forward', _('Go to my control panel'), :action => 'index', :controller => 'profile_editor', :profile => current_user.person.identifier %>
  7 + <%= button 'forward', _('Go to my control panel'), :action => 'index', :controller => 'profile_editor', :profile => current_person.identifier %>
8 8 <%= button 'forward', _('Go to my enterprise control panel') % @enterprise.name, :action => 'index', :controller => 'profile_editor', :profile => @enterprise.identifier %>
9 9 <% end %>
10 10 <% end %>
... ...
app/views/enterprise_registration/basic_information.html.erb
... ... @@ -9,7 +9,7 @@
9 9 </div>
10 10  
11 11 <%= button_bar do %>
12   - <%= button :back, _('Go back'), { :profile => current_user.person.identifier, :action=>"enterprises", :controller=>"profile" }%>
  12 + <%= button :back, _('Go back'), { :profile => current_person.identifier, :action=>"enterprises", :controller=>"profile" }%>
13 13 <% end %>
14 14 <% else %>
15 15 <div class='atention'>
... ... @@ -37,7 +37,7 @@
37 37 <%= template_options(:enterprises, 'create_enterprise')%>
38 38  
39 39 <%= button_bar do %>
40   - <%= submit_button('next', _('Next'), :cancel => {:profile => current_user.person.identifier, :action=>"enterprises", :controller=>"profile"}) %>
  40 + <%= submit_button('next', _('Next'), :cancel => {:profile => current_person.identifier, :action=>"enterprises", :controller=>"profile"}) %>
41 41 <% end %>
42 42 <% end %>
43 43 <% end %>
... ...
app/views/environment_role_manager/affiliate.html.erb
... ... @@ -2,7 +2,7 @@
2 2  
3 3 <%= form_tag( {:action => 'give_role'}, {:method => :post}) do %>
4 4 <%= select_tag 'role', options_for_select(@roles.map{|r|[r.name,r.id]}) %>
5   - <%= hidden_field_tag 'person', current_user.person.id %>
  5 + <%= hidden_field_tag 'person', current_person.id %>
6 6 <%= button_bar do %>
7 7 <%= submit_button('affiliate', _('Affiliate', :cancel => {:action => 'index'}) %>
8 8 <% end %>
... ...
app/views/layouts/_user.html.erb
1 1 <div id="user">
2 2 <% user = (session[:user] && User.find_by(id: session[:user])) || nil %>
  3 + <% user ||= (session[:external] && User.new(:external_person_id => session[:external])) || nil %>
3 4 <% if user.present? %>
4 5 <% user = user.person %>
5 6 <span class='logged-in'>
... ...
app/views/profile_members/affiliate.html.erb
... ... @@ -2,7 +2,7 @@
2 2  
3 3 <%= form_tag( {:action => 'give_role'}, {:method => :post}) do %>
4 4 <%= select_tag 'role', options_for_select(@roles.map{|r|[r.name,r.id]}) %>
5   - <%= hidden_field_tag 'person', current_user.person.id %>
  5 + <%= hidden_field_tag 'person', current_person.id %>
6 6 <%= button_bar do %>
7 7 <%= submit_button('affiliate', _('Affiliate'), :cancel => {:action => 'index'}) %>
8 8 <% end %>
... ...
config/routes.rb
... ... @@ -87,6 +87,9 @@ Noosfero::Application.routes.draw do
87 87 # comments
88 88 match 'profile/:profile/comment/:action/:id', controller: 'comment', profile: /#{Noosfero.identifier_format_in_url}/i, via: :all
89 89  
  90 + # icon
  91 + match 'profile/:profile/icon(/:size)', controller: 'profile', action: 'icon', size: /(big|minor|thumb|portrait|icon)/, profile: /#{Noosfero.identifier_format_in_url}/i, via: :get
  92 +
90 93 # public profile information
91 94 match 'profile/:profile(/:action(/:id))', controller: 'profile', action: 'index', id: /[^\/]*/, profile: /#{Noosfero.identifier_format_in_url}/i, as: :profile, via: :all
92 95  
... ...
db/migrate/20160420125236_add_type_to_polymorphic_profile_associations.rb 0 → 100644
... ... @@ -0,0 +1,17 @@
  1 +class AddTypeToPolymorphicProfileAssociations < ActiveRecord::Migration
  2 + def up
  3 + add_column :tasks, :reported_type, :string
  4 + add_column :abuse_reports, :reporter_type, :string
  5 + add_column :comments, :author_type, :string
  6 +
  7 + update("UPDATE tasks SET reported_type='Profile'")
  8 + update("UPDATE abuse_reports SET reporter_type='Person'")
  9 + update("UPDATE comments SET author_type='Person'")
  10 + end
  11 +
  12 + def down
  13 + remove_column :abuse_complaints, :reported_type
  14 + remove_column :abuse_reports, :reporter_type
  15 + remove_column :comments, :author_type
  16 + end
  17 +end
... ...
db/migrate/20160420140141_create_external_person.rb 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +class CreateExternalPerson < ActiveRecord::Migration
  2 + def change
  3 + create_table :external_people do |t|
  4 + t.string :name
  5 + t.string :identifier
  6 + t.string :source
  7 + t.string :email
  8 + t.integer :environment_id
  9 + t.boolean :visible, default: true
  10 + t.datetime :created_at
  11 + t.datetime :updated_at
  12 + end
  13 + end
  14 +end
... ...
db/schema.rb
... ... @@ -23,6 +23,7 @@ ActiveRecord::Schema.define(version: 20160422163123) do
23 23 t.text "reason"
24 24 t.datetime "created_at"
25 25 t.datetime "updated_at"
  26 + t.string "reporter_type"
26 27 end
27 28  
28 29 create_table "action_tracker", force: :cascade do |t|
... ... @@ -289,6 +290,7 @@ ActiveRecord::Schema.define(version: 20160422163123) do
289 290 t.string "user_agent"
290 291 t.string "referrer"
291 292 t.text "settings"
  293 + t.string "author_type"
292 294 end
293 295  
294 296 add_index "comments", ["source_id", "spam"], name: "index_comments_on_source_id_and_spam", using: :btree
... ... @@ -404,6 +406,14 @@ ActiveRecord::Schema.define(version: 20160422163123) do
404 406 t.boolean "disable_feed_ssl", default: false
405 407 end
406 408  
  409 + create_table "external_environments", force: :cascade do |t|
  410 + t.string "name"
  411 + t.string "url"
  412 + t.string "identifier"
  413 + t.string "screenshot"
  414 + t.string "thumbnail"
  415 + end
  416 +
407 417 create_table "external_feeds", force: :cascade do |t|
408 418 t.string "feed_title"
409 419 t.datetime "fetched_at"
... ... @@ -421,6 +431,17 @@ ActiveRecord::Schema.define(version: 20160422163123) do
421 431 add_index "external_feeds", ["enabled"], name: "index_external_feeds_on_enabled", using: :btree
422 432 add_index "external_feeds", ["fetched_at"], name: "index_external_feeds_on_fetched_at", using: :btree
423 433  
  434 + create_table "external_people", force: :cascade do |t|
  435 + t.string "name"
  436 + t.string "identifier"
  437 + t.string "source"
  438 + t.string "email"
  439 + t.integer "environment_id"
  440 + t.boolean "visible", default: true
  441 + t.datetime "created_at"
  442 + t.datetime "updated_at"
  443 + end
  444 +
424 445 create_table "favorite_enterprise_people", force: :cascade do |t|
425 446 t.integer "person_id"
426 447 t.integer "enterprise_id"
... ... @@ -432,14 +453,6 @@ ActiveRecord::Schema.define(version: 20160422163123) do
432 453 add_index "favorite_enterprise_people", ["person_id", "enterprise_id"], name: "index_favorite_enterprise_people_on_person_id_and_enterprise_id", using: :btree
433 454 add_index "favorite_enterprise_people", ["person_id"], name: "index_favorite_enterprise_people_on_person_id", using: :btree
434 455  
435   - create_table "external_environments", force: :cascade do |t|
436   - t.string "name"
437   - t.string "url"
438   - t.string "identifier"
439   - t.string "screenshot"
440   - t.string "thumbnail"
441   - end
442   -
443 456 create_table "friendships", force: :cascade do |t|
444 457 t.integer "person_id"
445 458 t.integer "friend_id"
... ... @@ -797,6 +810,7 @@ ActiveRecord::Schema.define(version: 20160422163123) do
797 810 t.boolean "spam", default: false
798 811 t.integer "responsible_id"
799 812 t.integer "closed_by_id"
  813 + t.string "reported_type"
800 814 end
801 815  
802 816 add_index "tasks", ["requestor_id"], name: "index_tasks_on_requestor_id", using: :btree
... ...
features/external_login.feature 0 → 100644
... ... @@ -0,0 +1,55 @@
  1 +Feature: external login
  2 + As a user
  3 + I want to login using an account from a federated network
  4 + In order to view pages logged in
  5 +
  6 + @selenium
  7 + Scenario: login from portal homepage
  8 + Given feature "allow_change_of_redirection_after_login" is disabled on environment
  9 + And the following external environments
  10 + | identifier | name | url |
  11 + | test | Test | http://federated.noosfero.org |
  12 + And the following external users
  13 + | login |
  14 + | joaosilva@federated.noosfero.org |
  15 + And I am not logged in
  16 + And I go to the homepage
  17 + And I follow "Login"
  18 + And I fill in the following:
  19 + | Username / Email | joaosilva@federated.noosfero.org |
  20 + | Password | 123456 |
  21 + When I press "Log in"
  22 + Then I should be on the homepage
  23 + And I should be externally logged in as "joaosilva@federated.noosfero.org"
  24 +
  25 + @selenium
  26 + Scenario: not login from portal homepage
  27 + Given feature "allow_change_of_redirection_after_login" is disabled on environment
  28 + And the following external environments
  29 + | identifier | name | url |
  30 + | test | Test | http://federated.noosfero.org |
  31 + And I am not logged in
  32 + And I go to the homepage
  33 + And I follow "Login"
  34 + And I fill in the following:
  35 + | Username / Email | joaosilva@federated.noosfero.org |
  36 + | Password | 123456 |
  37 + When I press "Log in"
  38 + Then I should be on /account/login
  39 + And I should not be externally logged in as "joaosilva@federated.noosfero.org"
  40 +
  41 + @selenium
  42 + Scenario: not login if network is not whitelisted
  43 + Given feature "allow_change_of_redirection_after_login" is disabled on environment
  44 + And the following external users
  45 + | login |
  46 + | joaosilva@federated.noosfero.org |
  47 + And I am not logged in
  48 + And I go to the homepage
  49 + And I follow "Login"
  50 + And I fill in the following:
  51 + | Username / Email | joaosilva@federated.noosfero.org |
  52 + | Password | 123456 |
  53 + When I press "Log in"
  54 + Then I should be on /account/login
  55 + And I should not be externally logged in as "joaosilva@federated.noosfero.org"
... ...
features/step_definitions/noosfero_steps.rb
... ... @@ -679,3 +679,36 @@ Given /^the field (.*) is public for all users$/ do |field|
679 679 person.save!
680 680 end
681 681 end
  682 +
  683 +Given /^the following external users?$/ do |table|
  684 + table.hashes.each do |item|
  685 + person_data = item.dup
  686 + username, domain = person_data['login'].split('@')
  687 + response = OpenStruct.new(
  688 + code: '200',
  689 + body: {
  690 + user: {
  691 + email: username + '@ema.il',
  692 + person: {
  693 + identifier: username,
  694 + name: username,
  695 + created_at: Time.now,
  696 + }
  697 + }
  698 + }.to_json
  699 + )
  700 + Net::HTTP.stub(:post_form).and_return(response)
  701 + end
  702 +end
  703 +
  704 +Then /^I should be externally logged in as "([^@]+)@(.+)"$/ do |username, domain|
  705 + visit '/'
  706 + url = 'http://' + domain + '/' + username
  707 + page.should have_xpath("//a[@href=\"#{url}\"]")
  708 +end
  709 +
  710 +Then /^I should not be externally logged in as "([^@]+)@(.+)"$/ do |username, domain|
  711 + visit '/'
  712 + url = 'http://' + domain + '/' + username
  713 + page.should_not have_xpath("//a[@href=\"#{url}\"]")
  714 +end
... ...
features/support/env.rb
... ... @@ -8,6 +8,7 @@ ENV[&quot;RAILS_ENV&quot;] ||= &quot;cucumber&quot;
8 8  
9 9 require File.expand_path(File.dirname(__FILE__) + '/../../config/environment')
10 10 require 'cucumber/rails'
  11 +require 'cucumber/rspec/doubles'
11 12  
12 13 Capybara.ignore_hidden_elements = true
13 14  
... ...
plugins/organization_ratings/controllers/organization_ratings_plugin_profile_controller.rb
... ... @@ -38,7 +38,7 @@ class OrganizationRatingsPluginProfileController &lt; ProfileController
38 38  
39 39 def create_new_rate
40 40 @rating = OrganizationRating.new(params[:organization_rating])
41   - @rating.person = current_user.person
  41 + @rating.person = current_person
42 42 @rating.organization = profile
43 43 @rating.value = params[:organization_rating_value] if params[:organization_rating_value]
44 44  
... ...
plugins/organization_ratings/test/functional/organization_ratings_plugin_profile_controller_test.rb
... ... @@ -173,6 +173,7 @@ class OrganizationRatingsPluginProfileControllerTest &lt; ActionController::TestCas
173 173  
174 174 logout
175 175 @controller.stubs(:logged_in?).returns(false)
  176 + @controller.stubs(:current_user).returns(nil)
176 177  
177 178 get :new_rating, profile: @community.identifier
178 179 assert_no_tag :tag => 'p', :content => /Report waiting for approval/, :attributes => {:class =>/comment-rejected-msg/}
... ...
plugins/organization_ratings/views/organization_ratings_plugin_profile/_new_rating_fields.html.erb
... ... @@ -5,11 +5,11 @@
5 5  
6 6 <div class="star-profile-information">
7 7 <div class="star-profile-image">
8   - <%= link_to profile_image(current_user.person, :portrait), current_user.person.url %>
  8 + <%= link_to profile_image(current_person, :portrait), current_person.url %>
9 9 </div>
10 10  
11 11 <div class="star-profile-name">
12   - <%= link_to current_user.person.name, current_user.person.url %>
  12 + <%= link_to current_person.name, current_person.url %>
13 13 </div>
14 14 </div>
15 15  
... ... @@ -55,14 +55,14 @@
55 55 </div>
56 56 <% elsif env_organization_ratings_config.vote_once %>
57 57 <div class="star-rate-form rating-vote-once">
58   - <%= _("Hi, %s! The administrators set that you can vote") % current_user.name %>
  58 + <%= _("Hi, %s! The administrators set that you can vote") % current_person.name %>
59 59 <strong><%= _("only once") %></strong>
60 60 <%= _("for this %s.") % profile.class.name.downcase %>
61 61 <%= render :partial => 'shared/rating_button', :locals => { :disabled => true } %>
62 62 </div>
63 63 <% else %>
64 64 <div class="star-rate-form rating-cooldown">
65   - <%= _("Hi, %s! The administrators set the minimum time of") % current_user.name %>
  65 + <%= _("Hi, %s! The administrators set the minimum time of") % current_person.name %>
66 66 <strong><%= _("%d hour(s)") % env_organization_ratings_config.cooldown %></strong>
67 67 <%= _("between each evaluation.") %>
68 68  
... ...
plugins/organization_ratings/views/shared/_make_report_block.html.erb
1   -<% logged_in_image = link_to profile_image(current_user.person, :portrait), current_user.person.url if current_user %>
2   -<% logged_in_name = link_to current_user.person.name, current_user.person.url if current_user %>
3   -<% logged_out_image = image_tag('plugins/organization_ratings/images/user-not-logged.png') %>
  1 +<% logged_in_image = link_to profile_image(current_person, :portrait), current_person.url if current_user %>
  2 +<% logged_in_name = link_to current_person.name, current_person.url if current_user %>
  3 +<% logged_out_image = image_tag('plugins/organization_ratings/public/images/user-not-logged.png') %>
4 4  
5 5 <div class="make-report-block">
6 6 <div class="star-profile-information">
... ... @@ -26,4 +26,4 @@
26 26 </div>
27 27 <% end %>
28 28 </div>
29   -</div>
30 29 \ No newline at end of file
  30 +</div>
... ...
plugins/responsive/lib/ext/application_helper.rb
... ... @@ -140,7 +140,7 @@ module ApplicationHelper
140 140 [s_('contents|Most commented'), {host: host, controller: :search, action: :contents, filter: 'more_comments'}],
141 141 ]
142 142 if logged_in?
143   - links.push [_('New content'), '', modal_options({href: url_for({controller: 'cms', action: 'new', profile: current_user.login, cms: true})})]
  143 + links.push [_('New content'), '', modal_options({href: url_for({controller: 'cms', action: 'new', profile: current_person.identifier, cms: true})})]
144 144 end
145 145  
146 146 content_tag :li, class: 'dropdown' do
... ... @@ -170,8 +170,8 @@ module ApplicationHelper
170 170 [s_('people|More popular'), {host: host, controller: :search, action: :people, filter: 'more_popular'}],
171 171 ]
172 172 if logged_in?
173   - links.push [_('My friends'), {profile: current_user.login, controller: 'friends'}]
174   - links.push [_('Invite friends'), {profile: current_user.login, controller: 'invite', action: 'friends'}]
  173 + links.push [_('My friends'), {profile: current_person.identifier, controller: 'friends'}]
  174 + links.push [_('Invite friends'), {profile: current_person.identifier, controller: 'invite', action: 'friends'}]
175 175 end
176 176  
177 177 content_tag :li, class: 'dropdown' do
... ... @@ -201,8 +201,8 @@ module ApplicationHelper
201 201 [s_('communities|More popular'), {host: host, controller: :search, action: :communities, filter: 'more_popular'}],
202 202 ]
203 203 if logged_in?
204   - links.push [_('My communities'), {profile: current_user.login, controller: 'memberships'}]
205   - links.push [_('New community'), {profile: current_user.login, controller: 'memberships', action: 'new_community'}]
  204 + links.push [_('My communities'), {profile: current_person.identifier, controller: 'memberships'}]
  205 + links.push [_('New community'), {profile: current_person.identifier, controller: 'memberships', action: 'new_community'}]
206 206 end
207 207  
208 208 content_tag :li, class: 'dropdown' do
... ...
plugins/sniffer/views/blocks/interests.html.erb
... ... @@ -8,7 +8,7 @@
8 8 </ul>
9 9  
10 10 <div>
11   - <% if logged_in? and (current_user.person.is_admin?(environment) or profile.admins.include?(current_user.person)) %>
  11 + <% if logged_in? and (current_person.is_admin?(environment) or profile.admins.include?(current_person)) %>
12 12 <%= _('Edit %{inputs} and %{block.interests}') % {
13 13 :inputs => link_to(_("products' inputs"), :controller => :manage_products, :action => :index),
14 14 :interests => link_to(_('declared interests'), :controller => :sniffer_plugin_myprofile, :action => :edit),
... ...
plugins/solr/lib/solr_plugin/search_helper.rb
... ... @@ -12,9 +12,9 @@ module SolrPlugin::SearchHelper
12 12 :products => ActiveSupport::OrderedHash[ :none, {:label => _('Relevance')},
13 13 :more_recent, {:label => c_('More recent'), :solr_opts => {:sort => 'updated_at desc, score desc'}},
14 14 :name, {:label => _('Name'), :solr_opts => {:sort => 'solr_plugin_name_sortable asc'}},
15   - :closest, {:label => _('Closest to me'), :if => proc{ logged_in? && (profile=current_user.person).lat && profile.lng },
  15 + :closest, {:label => _('Closest to me'), :if => proc{ logged_in? && (profile=current_person).lat && profile.lng },
16 16 :solr_opts => {:sort => "geodist() asc",
17   - :latitude => proc{ current_user.person.lat }, :longitude => proc{ current_user.person.lng }}},
  17 + :latitude => proc{ current_person.lat }, :longitude => proc{ current_person.lng }}},
18 18 ],
19 19 :events => ActiveSupport::OrderedHash[ :none, {:label => _('Relevance')},
20 20 :name, {:label => _('Name'), :solr_opts => {:sort => 'solr_plugin_name_sortable asc'}},
... ...
test/api/federation/webfinger_test.rb 0 → 100644
... ... @@ -0,0 +1,53 @@
  1 +require_relative '../test_helper'
  2 +
  3 +class WebfingerTest < ActiveSupport::TestCase
  4 + def setup
  5 + Domain.create(name: 'example.com')
  6 + Environment.default.domains << Domain.last
  7 + User.create(login: 'ze', email: 'ze@localdomain.localdomain',
  8 + password: 'zeze', password_confirmation: 'zeze')
  9 + end
  10 +
  11 + should 'return correct user via webfinger url' do
  12 + get '.well-known/webfinger?resource=acct%3Aze%40example.com'
  13 + webfinger = JSON.parse(last_response.body)
  14 + assert_equal 200, last_response.status
  15 + assert_equal webfinger['subject'], 'acct:ze@example.com'
  16 + end
  17 +
  18 + should 'not return json when user not found' do
  19 + invalid_user = 'invalid_user_in_url'
  20 + get ".well-known/webfinger?resource=acct%3A#{invalid_user}%40example.com"
  21 + assert_equal 404, last_response.status
  22 + end
  23 +
  24 + should 'return correct article via webfinger url' do
  25 + a = fast_create(Article, name: 'my article', profile_id: 1)
  26 + get ".well-known/webfinger?resource=http://example.com/article/id/#{a.id}"
  27 + webfinger = JSON.parse(last_response.body)
  28 + assert_equal 200, last_response.status
  29 + assert_equal webfinger['subject'], "http://example.com/article/id/#{a.id}"
  30 + end
  31 +
  32 + should 'not return json when domain is invalid' do
  33 + invalid_domain = 'doest_not_exist.com'
  34 + get ".well-known/webfinger?resource=http://#{invalid_domain}/article/id/1"
  35 + assert_equal 404, last_response.status
  36 + end
  37 +
  38 + should 'not return json when entity is not found' do
  39 + get '.well-known/webfinger?resource=http://example.com/article/id/999999'
  40 + assert_equal 404, last_response.status
  41 + end
  42 +
  43 + should 'not return json when entity does not exist' do
  44 + get '.well-known/webfinger?resource=http://example.com/doest_not_exist/id/1'
  45 + assert_equal 404, last_response.status
  46 + end
  47 +
  48 + should 'not return json when request is not http' do
  49 + not_http_url = 'kkttc://example.com/article/id/1'
  50 + get ".well-known/webfinger?resource=#{not_http_url}"
  51 + assert_equal 404, last_response.status
  52 + end
  53 +end
... ...
test/api/people_test.rb
... ... @@ -369,6 +369,44 @@ class PeopleTest &lt; ActiveSupport::TestCase
369 369 assert_equal "www.blog.org", json['person']['additional_data']['Custom Blog']
370 370 end
371 371  
  372 + should 'return portrait icon if size is not provided and there is a profile image' do
  373 + img = Image.create!(uploaded_data: fixture_file_upload('/files/rails.png', 'image/png'))
  374 + profile = fast_create(Person, image_id: img.id)
  375 +
  376 + get "/api/v1/people/#{profile.id}/icon?#{params.to_query}"
  377 + assert_equal 200, last_response.status
  378 + json = JSON.parse(last_response.body)
  379 + assert_match(/^https?:\/\/.*portrait\.png$/, json['icon'])
  380 + end
  381 +
  382 + should 'return icon in provided size if there is a profile image' do
  383 + img = Image.create!(uploaded_data: fixture_file_upload('/files/rails.png', 'image/png'))
  384 + profile = fast_create(Person, image_id: img.id)
  385 +
  386 + get "/api/v1/people/#{profile.id}/icon?#{params.to_query}&size=big"
  387 + assert_equal 200, last_response.status
  388 + json = JSON.parse(last_response.body)
  389 + assert_match(/^https?:\/\/.*big\.png$/, json['icon'])
  390 + end
  391 +
  392 + should 'return icon from gravatar without size if there is no profile image' do
  393 + profile = create_user('test-user').person
  394 +
  395 + get "/api/v1/people/#{profile.id}/icon?#{params.to_query}"
  396 + assert_equal 200, last_response.status
  397 + json = JSON.parse(last_response.body)
  398 + assert_match(/^https:\/\/www\.gravatar\.com.*size=64/, json['icon'])
  399 + end
  400 +
  401 + should 'return icon from gravatar with size if there is no profile image' do
  402 + profile = create_user('test-user').person
  403 +
  404 + get "/api/v1/people/#{profile.id}/icon?#{params.to_query}&size=big"
  405 + assert_equal 200, last_response.status
  406 + json = JSON.parse(last_response.body)
  407 + assert_match(/^https:\/\/www\.gravatar\.com.*size=150/, json['icon'])
  408 + end
  409 +
372 410 PERSON_ATTRIBUTES = %w(vote_count comments_count articles_count following_articles_count)
373 411  
374 412 PERSON_ATTRIBUTES.map do |attribute|
... ...
test/functional/comment_controller_test.rb
... ... @@ -487,7 +487,7 @@ class CommentControllerTest &lt; ActionController::TestCase
487 487 should 'edit comment from a page' do
488 488 login_as profile.identifier
489 489 page = profile.articles.create!(:name => 'myarticle', :body => 'the body of the text')
490   - comment = fast_create(Comment, :body => 'Original comment', :source_id => page.id, :source_type => 'Article', :author_id => profile.id)
  490 + comment = fast_create(Comment, :body => 'Original comment', :source_id => page.id, :source_type => 'Article', :author_id => profile.id, :author_type => 'Person')
491 491  
492 492 get :edit, :id => comment.id, :profile => profile.identifier, :comment => { :body => 'Comment edited' }
493 493 assert_tag :tag => 'textarea', :attributes => {:id => 'comment_body'}, :content => /Original comment/
... ... @@ -522,7 +522,7 @@ class CommentControllerTest &lt; ActionController::TestCase
522 522 should 'be able to update a comment' do
523 523 login_as profile.identifier
524 524 page = profile.articles.create!(:name => 'myarticle', :body => 'the body of the text', :accept_comments => false)
525   - comment = fast_create(Comment, :body => 'Original comment', :source_id => page.id, :source_type => 'Article', :author_id => profile)
  525 + comment = fast_create(Comment, :body => 'Original comment', :source_id => page.id, :source_type => 'Article', :author_id => profile, :author_type => 'Person')
526 526  
527 527 xhr :post, :update, :id => comment.id, :profile => profile.identifier, :comment => { :body => 'Comment edited' }
528 528 assert ActiveSupport::JSON.decode(@response.body)["ok"], "attribute ok expected to be true"
... ...
test/functional/profile_controller_test.rb
... ... @@ -1932,4 +1932,37 @@ class ProfileControllerTest &lt; ActionController::TestCase
1932 1932 assert_redirected_to :controller => 'account', :action => 'login'
1933 1933 end
1934 1934  
  1935 + should 'return portrait icon if size is not provided and there is a profile image' do
  1936 + img = Image.create!(uploaded_data: fixture_file_upload('/files/rails.png', 'image/png'))
  1937 + profile = fast_create(Person, image_id: img.id)
  1938 +
  1939 + get :icon, profile: profile.identifier, size: nil
  1940 + assert_response :success
  1941 + assert_equal 'image/png', @response.header['Content-Type']
  1942 + assert File.exists?(assigns(:file))
  1943 + end
  1944 +
  1945 + should 'return icon in provided size if there is a profile image' do
  1946 + img = Image.create!(uploaded_data: fixture_file_upload('/files/rails.png', 'image/png'))
  1947 + profile = fast_create(Person, image_id: img.id)
  1948 +
  1949 + get :icon, profile: profile.identifier, size: :big
  1950 + assert_response :success
  1951 + assert_equal 'image/png', @response.header['Content-Type']
  1952 + assert File.exists?(assigns(:file))
  1953 + end
  1954 +
  1955 + should 'return icon from gravatar without size if there is no profile image' do
  1956 + profile = fast_create(Person)
  1957 +
  1958 + get :icon, profile: profile.identifier
  1959 + assert_redirected_to /^https:\/\/www\.gravatar\.com\/.*/
  1960 + end
  1961 +
  1962 + should 'return icon from gravatar with size if there is no profile image' do
  1963 + profile = fast_create(Person)
  1964 +
  1965 + get :icon, profile: profile.identifier, size: :thumb
  1966 + assert_redirected_to /^https:\/\/www\.gravatar\.com\/.*/
  1967 + end
1935 1968 end
... ...
test/integration/routing_test.rb
... ... @@ -276,4 +276,11 @@ class RoutingTest &lt; ActionDispatch::IntegrationTest
276 276 assert_routing('/profile/~', :controller => 'profile', :profile => '~', :action => 'index')
277 277 end
278 278  
  279 + should 'have route to profile icon without size' do
  280 + assert_routing('/profile/ze/icon', controller: 'profile', profile: 'ze', action: 'icon')
  281 + end
  282 +
  283 + should 'have route to profile icon with supported size' do
  284 + assert_routing('/profile/ze/icon/big', controller: 'profile', profile: 'ze', action: 'icon', size: 'big')
  285 + end
279 286 end
... ...
test/unit/comment_test.rb
... ... @@ -23,17 +23,14 @@ class CommentTest &lt; ActiveSupport::TestCase
23 23 end
24 24 end
25 25  
26   - should 'record authenticated author' do
  26 + should 'record authenticated author polymorphically' do
27 27 c = Comment.new
28   - assert_raise ActiveRecord::AssociationTypeMismatch do
29   - c.author = 1
30   - end
31   - assert_raise ActiveRecord::AssociationTypeMismatch do
32   - c.author = Profile
33   - end
34 28 assert_nothing_raised do
35 29 c.author = Person.new
36 30 end
  31 + assert_nothing_raised do
  32 + c.author = ExternalPerson.new
  33 + end
37 34 end
38 35  
39 36 should 'record unauthenticated author' do
... ...
test/unit/external_person_test.rb 0 → 100644
... ... @@ -0,0 +1,137 @@
  1 +# encoding: UTF-8
  2 +require_relative "../test_helper"
  3 +
  4 +class ExternalPersonTest < ActiveSupport::TestCase
  5 + fixtures :environments
  6 +
  7 + def setup
  8 + @external_person = ExternalPerson.create!(identifier: 'johnlock',
  9 + name: 'John Lock',
  10 + source: 'anerenvironment.org',
  11 + email: 'john@lock.org',
  12 + created_at: Date.yesterday
  13 + )
  14 + end
  15 +
  16 + should 'have no permissions' do
  17 + assert_equivalent [], @external_person.role_assignments
  18 + refute @external_person.has_permission?(:manage_friends)
  19 + end
  20 +
  21 + should 'have no suggested profiles' do
  22 + assert_equivalent [], @external_person.suggested_communities
  23 + assert_equivalent [], @external_person.suggested_people
  24 + assert_equivalent [], @external_person.suggested_profiles
  25 + end
  26 +
  27 + should 'have no friendships' do
  28 + refute @external_person.add_friend(fast_create(Person))
  29 + assert_equivalent [], @external_person.friendships
  30 + end
  31 +
  32 + should 'not be a member of any communities' do
  33 + community = fast_create(Community)
  34 + refute community.add_member(@external_person)
  35 + assert_equivalent [], @external_person.memberships
  36 + end
  37 +
  38 + should 'not have any favorite enterprises' do
  39 + assert_equivalent [], @external_person.favorite_enterprises
  40 + end
  41 +
  42 + should 'be a person' do
  43 + assert @external_person.person?
  44 + end
  45 +
  46 + should 'not be a community, organization or enterprise' do
  47 + refute @external_person.community?
  48 + refute @external_person.enterprise?
  49 + refute @external_person.organization?
  50 + end
  51 +
  52 + should 'never be an admin for environments' do
  53 + refute @external_person.is_admin?
  54 + env = Environment.default
  55 + env.add_admin @external_person
  56 + refute @external_person.is_admin?(env)
  57 + assert_equivalent [], env.admins
  58 + end
  59 +
  60 + should 'redirect after login based on environment settings' do
  61 + assert_respond_to ExternalPerson.new, :preferred_login_redirection
  62 + environment = fast_create(Environment, :redirection_after_login => 'site_homepage')
  63 + profile = fast_create(ExternalPerson, :environment_id => environment.id)
  64 + assert_equal 'site_homepage', profile.preferred_login_redirection
  65 + end
  66 +
  67 + should 'have an avatar from its original environment' do
  68 + assert_match(/http:\/\/#{@external_person.source}\/.*/, @external_person.avatar)
  69 + end
  70 +
  71 + should 'generate a custom profile icon based on its avatar' do
  72 + assert_match(/http:\/\/#{@external_person.source}\/.*/, @external_person.profile_custom_icon)
  73 + end
  74 +
  75 + should 'have an url to its profile on its original environment' do
  76 + assert_match(/http:\/\/#{@external_person.source}\/profile\/.*/, @external_person.url)
  77 + end
  78 +
  79 + should 'have a public profile url' do
  80 + assert_match(/http:\/\/#{@external_person.source}\/profile\/.*/, @external_person.public_profile_url)
  81 + end
  82 +
  83 + should 'have an admin url to its profile on its original environment' do
  84 + assert_match(/http:\/\/#{@external_person.source}\/myprofile\/.*/, @external_person.admin_url)
  85 + end
  86 +
  87 + should 'never be a friend of another person' do
  88 + friend = fast_create(Person)
  89 + friend.add_friend @external_person
  90 + refute @external_person.is_a_friend?(friend)
  91 + refute friend.is_a_friend?(@external_person)
  92 + end
  93 +
  94 + should 'never send a friend request to another person' do
  95 + friend = fast_create(Person)
  96 + friend.add_friend @external_person
  97 + refute friend.already_request_friendship?(@external_person)
  98 + @external_person.add_friend(friend)
  99 + refute @external_person.already_request_friendship?(friend)
  100 + end
  101 +
  102 + should 'not follow another profile' do
  103 + friend = fast_create(Person)
  104 + friend.add_friend @external_person
  105 + refute @external_person.follows?(friend)
  106 + refute friend.follows?(@external_person)
  107 + end
  108 +
  109 + should 'have an image' do
  110 + assert_not_nil @external_person.image
  111 + end
  112 +
  113 + should 'profile image has public filename and mimetype' do
  114 + assert_not_nil @external_person.image.public_filename
  115 + assert_not_nil @external_person.image.content_type
  116 + end
  117 +
  118 + should 'respond to all instance methods in Profile' do
  119 + methods = Profile.public_instance_methods(false)
  120 + methods.each do |method|
  121 + # We test if ExternalPerson responds to same methods as Profile, but we
  122 + # skip methods generated by plugins, libs and validations, which are
  123 + # usually only used internally
  124 + assert_respond_to ExternalPerson.new, method.to_sym unless method =~ /type_name|^autosave_.*|^after_.*|^before_.*|validate_.*|^attribute_.*|.*_?tags?_?.*|^custom_value.*|^custom_context.*|^xss.*|bar/
  125 + end
  126 + end
  127 +
  128 + should 'respond to all instance methods in Person' do
  129 + methods = Person.public_instance_methods(false)
  130 + methods.each do |method|
  131 + # We test if ExternalPerson responds to same methods as Person, but we
  132 + # skip methods generated by plugins, libs and validations, which are
  133 + # usually only used internally
  134 + assert_respond_to ExternalPerson.new, method.to_sym unless method =~ /^autosave_.*|validate_.*|^before_.*|^after_.*|^assignment_.*|^(city|state)_.*/
  135 + end
  136 + end
  137 +end
... ...
test/unit/person_test.rb
... ... @@ -1831,9 +1831,9 @@ class PersonTest &lt; ActiveSupport::TestCase
1831 1831 p1 = fast_create(Person)
1832 1832 p2 = fast_create(Person)
1833 1833 article = fast_create(Article)
1834   - c1 = fast_create(Comment, :source_id => article.id, :author_id => p1.id)
1835   - c2 = fast_create(Comment, :source_id => article.id, :author_id => p2.id)
1836   - c3 = fast_create(Comment, :source_id => article.id, :author_id => p1.id)
  1834 + c1 = fast_create(Comment, :source_id => article.id, :author_id => p1.id, :author_type => 'Profile')
  1835 + c2 = fast_create(Comment, :source_id => article.id, :author_id => p2.id, :author_type => 'Profile')
  1836 + c3 = fast_create(Comment, :source_id => article.id, :author_id => p1.id, :author_type => 'Profile')
1837 1837  
1838 1838 assert_equivalent [c1,c3], p1.comments
1839 1839 end
... ...
test/unit/user_test.rb
... ... @@ -759,6 +759,14 @@ class UserTest &lt; ActiveSupport::TestCase
759 759 end
760 760 end
761 761  
  762 + should 'get person or external person' do
  763 + user = create_user('new_user')
  764 + assert_kind_of Person, user.person
  765 + user.external_person_id = ExternalPerson.create!(identifier: 'new_user', name: 'New User', email: 'newuser@usermail.com', source: 'federated.noosfero.com', created_at: Date.today).id
  766 + assert_kind_of ExternalPerson, user.person
  767 + assert_kind_of Person, user.person_without_external
  768 + end
  769 +
762 770 protected
763 771 def new_user(options = {})
764 772 user = User.new({ :login => 'quire', :email => 'quire@example.com', :password => 'quire', :password_confirmation => 'quire' }.merge(options))
... ...
vendor/plugins/noosfero_caching/init.rb
... ... @@ -27,7 +27,7 @@ module NoosferoHttpCaching
27 27 end
28 28  
29 29 def noosfero_session_check
30   - headers["X-Noosfero-Auth"] = (session[:user] != nil).to_s
  30 + headers["X-Noosfero-Auth"] = (session[:user] != nil || session[:external] != nil).to_s
31 31 end
32 32  
33 33 class Middleware
... ...