Browse Source

userstamps, subdomains authorization, full Squeel compatibility, tests refactor

pull/1/head
Nicolae Claudius 13 years ago
parent
commit
77dbf6ef85
  1. 1
      Gemfile
  2. 35
      Gemfile.lock
  3. 6
      app/models/ability.rb
  4. 17
      app/models/domain.rb
  5. 2
      app/models/permission.rb
  6. 2
      app/models/record.rb
  7. 5
      app/models/user.rb
  8. 5
      config/initializers/cancan.rb
  9. 31
      db/migrate/20120115174339_add_userstamps_to_models.rb
  10. 44
      spec/models/ability_spec.rb
  11. 18
      spec/models/domain_spec.rb
  12. 8
      spec/models/record_spec.rb
  13. 41
      spec/support/shared_context/data.rb

1
Gemfile

@ -11,6 +11,7 @@ gem 'devise', '~> 1.4.5'
gem 'cancan', '~> 1.6.5' gem 'cancan', '~> 1.6.5'
gem 'squeel', '~> 0.9.3' gem 'squeel', '~> 0.9.3'
gem 'sentient_user', '~> 0.3.2' gem 'sentient_user', '~> 0.3.2'
gem 'userstamp_basic', '~> 0.1.0'
gem 'active_scaffold', '~> 3.1.0', :git => 'https://github.com/activescaffold/active_scaffold.git' # :path => '/home/clyfe/dev/active_scaffold' gem 'active_scaffold', '~> 3.1.0', :git => 'https://github.com/activescaffold/active_scaffold.git' # :path => '/home/clyfe/dev/active_scaffold'
gem 'pjax_rails', '~> 0.1.10' gem 'pjax_rails', '~> 0.1.10'
gem 'validates_hostname', '~> 1.0.0', :git => 'https://github.com/KimNorgaard/validates_hostname.git' gem 'validates_hostname', '~> 1.0.0', :git => 'https://github.com/KimNorgaard/validates_hostname.git'

35
Gemfile.lock

@ -6,9 +6,9 @@ GIT
GIT GIT
remote: https://github.com/activescaffold/active_scaffold.git remote: https://github.com/activescaffold/active_scaffold.git
revision: 7ece16b812891b155818ba466c4490b631bccffb revision: 81a0db7cd2be4405ee097e59fde3dd5efb4ae66b
specs: specs:
active_scaffold (3.1.13) active_scaffold (3.1.14)
rails (~> 3.1.0) rails (~> 3.1.0)
GEM GEM
@ -62,7 +62,7 @@ GEM
rack-test (>= 0.5.4) rack-test (>= 0.5.4)
selenium-webdriver (~> 2.0) selenium-webdriver (~> 2.0)
xpath (~> 0.1.4) xpath (~> 0.1.4)
childprocess (0.2.4) childprocess (0.3.0)
ffi (~> 1.0.6) ffi (~> 1.0.6)
chunky_png (1.2.5) chunky_png (1.2.5)
coffee-rails (3.1.1) coffee-rails (3.1.1)
@ -72,7 +72,7 @@ GEM
coffee-script-source coffee-script-source
execjs execjs
coffee-script-source (1.2.0) coffee-script-source (1.2.0)
compass (0.12.alpha.3) compass (0.12.alpha.4)
chunky_png (~> 1.2) chunky_png (~> 1.2)
fssm (>= 0.2.7) fssm (>= 0.2.7)
sass (~> 3.1) sass (~> 3.1)
@ -87,17 +87,17 @@ GEM
erubis (2.7.0) erubis (2.7.0)
execjs (1.2.13) execjs (1.2.13)
multi_json (~> 1.0) multi_json (~> 1.0)
factory_girl (2.3.2) factory_girl (2.4.0)
activesupport activesupport
factory_girl_rails (1.4.0) factory_girl_rails (1.5.0)
factory_girl (~> 2.3.0) factory_girl (~> 2.4.0)
railties (>= 3.0.0) railties (>= 3.0.0)
faker (1.0.1) faker (1.0.1)
i18n (~> 0.4) i18n (~> 0.4)
ffi (1.0.11) ffi (1.0.11)
fssm (0.2.7) fssm (0.2.8.1)
gem_plugin (0.2.3) gem_plugin (0.2.3)
guard (0.9.4) guard (0.10.0)
ffi (>= 0.5.0) ffi (>= 0.5.0)
thor (~> 0.14.6) thor (~> 0.14.6)
guard-rspec (0.4.5) guard-rspec (0.4.5)
@ -111,7 +111,7 @@ GEM
jquery-rails (1.0.19) jquery-rails (1.0.19)
railties (~> 3.0) railties (~> 3.0)
thor (~> 0.14) thor (~> 0.14)
json (1.6.4) json (1.6.5)
libnotify (0.5.9) libnotify (0.5.9)
libv8 (3.3.10.4) libv8 (3.3.10.4)
mail (2.3.0) mail (2.3.0)
@ -129,20 +129,20 @@ GEM
net-ssh (>= 1.99.1) net-ssh (>= 1.99.1)
net-sftp (2.0.5) net-sftp (2.0.5)
net-ssh (>= 2.0.9) net-ssh (>= 2.0.9)
net-ssh (2.2.1) net-ssh (2.3.0)
net-ssh-gateway (1.1.0) net-ssh-gateway (1.1.0)
net-ssh (>= 1.99.1) net-ssh (>= 1.99.1)
nilify_blanks (1.0.0) nilify_blanks (1.0.0)
activerecord (>= 3.0.0) activerecord (>= 3.0.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
nokogiri (1.5.0) nokogiri (1.5.0)
orm_adapter (0.0.5) orm_adapter (0.0.6)
pjax_rails (0.1.10) pjax_rails (0.1.10)
jquery-rails jquery-rails
polyamorous (0.5.0) polyamorous (0.5.0)
activerecord (~> 3.0) activerecord (~> 3.0)
polyglot (0.3.3) polyglot (0.3.3)
rack (1.3.5) rack (1.3.6)
rack-cache (1.1) rack-cache (1.1)
rack (>= 0.4) rack (>= 0.4)
rack-mount (0.8.3) rack-mount (0.8.3)
@ -194,8 +194,8 @@ GEM
railties (~> 3.1.0) railties (~> 3.1.0)
sass (~> 3.1.10) sass (~> 3.1.10)
tilt (~> 1.3.2) tilt (~> 1.3.2)
selenium-webdriver (2.15.0) selenium-webdriver (2.16.0)
childprocess (>= 0.2.1) childprocess (>= 0.2.5)
ffi (~> 1.0.9) ffi (~> 1.0.9)
multi_json (~> 1.0.4) multi_json (~> 1.0.4)
rubyzip rubyzip
@ -221,9 +221,11 @@ GEM
polyglot polyglot
polyglot (>= 0.3.1) polyglot (>= 0.3.1)
tzinfo (0.3.31) tzinfo (0.3.31)
uglifier (1.2.1) uglifier (1.2.2)
execjs (>= 0.3.0) execjs (>= 0.3.0)
multi_json (>= 1.0.2) multi_json (>= 1.0.2)
userstamp_basic (0.1.0)
sentient_user (>= 0.1.0)
warden (1.0.6) warden (1.0.6)
rack (>= 1.0) rack (>= 1.0)
xpath (0.1.4) xpath (0.1.4)
@ -266,4 +268,5 @@ DEPENDENCIES
squeel (~> 0.9.3) squeel (~> 0.9.3)
therubyracer therubyracer
uglifier uglifier
userstamp_basic (~> 0.1.0)
validates_hostname (~> 1.0.0)! validates_hostname (~> 1.0.0)!

6
app/models/ability.rb

@ -23,6 +23,12 @@ class Ability
# can manage shared domains and records # can manage shared domains and records
can :manage, Domain, :permissions.outer => {:user_id => user.id} can :manage, Domain, :permissions.outer => {:user_id => user.id}
can :manage, Record, :domain => {:permissions.outer => {:user_id => user.id}} can :manage, Record, :domain => {:permissions.outer => {:user_id => user.id}}
# can manage shared domains and records descendants
for domain in user.permitted_domains
can :manage, Domain, :name_reversed.matches => "#{domain.name_reversed}.%" # descendants
can :manage, Record, :domain => {:name_reversed.matches => "#{domain.name_reversed}.%"} # descendant's
end
end end
# See the wiki for details: https://github.com/ryanb/cancan/wiki/Defining-Abilities # See the wiki for details: https://github.com/ryanb/cancan/wiki/Defining-Abilities

17
app/models/domain.rb

@ -1,6 +1,7 @@
class Domain < ActiveRecord::Base class Domain < ActiveRecord::Base
set_inheritance_column "sti_disabled" set_inheritance_column "sti_disabled"
nilify_blanks nilify_blanks
stampable
# optional IP for create form, results in a type A record # optional IP for create form, results in a type A record
attr_accessor :ip attr_accessor :ip
@ -8,6 +9,7 @@ class Domain < ActiveRecord::Base
belongs_to :user, :inverse_of => :domain belongs_to :user, :inverse_of => :domain
has_many :records, :inverse_of => :domain, :dependent => :destroy has_many :records, :inverse_of => :domain, :dependent => :destroy
has_many :permissions, :inverse_of => :domain, :dependent => :destroy has_many :permissions, :inverse_of => :domain, :dependent => :destroy
has_many :permitted_users, :through => :permissions, :source => :user
cattr_reader :types cattr_reader :types
@@types = ['NATIVE', 'MASTER', 'SLAVE', 'SUPERSLAVE'] @@types = ['NATIVE', 'MASTER', 'SLAVE', 'SUPERSLAVE']
@ -49,8 +51,9 @@ class Domain < ActiveRecord::Base
# non-TLD validation # non-TLD validation
errors[:name] = "cannot be a TLD or a reserved domain" if Tld.include?(name) errors[:name] = "cannot be a TLD or a reserved domain" if Tld.include?(name)
# if parent domain is on our system, the user must own it # If parent domain is on our system, the user be permitted to manage current domain.
if parent_domain.present? && user.cannot?(:manage, parent_domain) # He either owns parent, or is permitted to current domain or to an ancestor.
if parent_domain.present? && can_be_managed_by_current_user?
errors[:name] = "issue, the parent domain `#{parent_domain.name}` is registered to another user" errors[:name] = "issue, the parent domain `#{parent_domain.name}` is registered to another user"
end end
end end
@ -69,6 +72,12 @@ class Domain < ActiveRecord::Base
end end
end end
# If current user present authorize it
# If current user not present, just allow (rake tasks etc)
def can_be_managed_by_current_user?
User.current.present? ? User.current.can?(:manage, self) : true
end
def slave?; self.type == 'SLAVE' end def slave?; self.type == 'SLAVE' end
before_create do before_create do
@ -116,6 +125,10 @@ class Domain < ActiveRecord::Base
scope :host_domains, where(:name => Settings.host_domains) scope :host_domains, where(:name => Settings.host_domains)
def subdomains
Domain.where(:name_reversed.matches => "#{name_reversed}.%")
end
def host_domain? def host_domain?
Settings.host_domains.include?(name) Settings.host_domains.include?(name)
end end

2
app/models/permission.rb

@ -1,4 +1,6 @@
class Permission < ActiveRecord::Base class Permission < ActiveRecord::Base
stampable
belongs_to :domain, :inverse_of => :permissions belongs_to :domain, :inverse_of => :permissions
belongs_to :user, :inverse_of => :permissions belongs_to :user, :inverse_of => :permissions

2
app/models/record.rb

@ -1,4 +1,6 @@
class Record < ActiveRecord::Base class Record < ActiveRecord::Base
stampable
belongs_to :domain, :inverse_of => :records belongs_to :domain, :inverse_of => :records
belongs_to :user, :inverse_of => :records belongs_to :user, :inverse_of => :records

5
app/models/user.rb

@ -1,5 +1,7 @@
class User < ActiveRecord::Base class User < ActiveRecord::Base
include SentientUser include SentientUser
stampable
# Include default devise modules. Others available are: # Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :timeoutable and :omniauthable # :token_authenticatable, :encryptable, :timeoutable and :omniauthable
devise :database_authenticatable, devise :database_authenticatable,
@ -19,6 +21,7 @@ class User < ActiveRecord::Base
has_many :domains, :inverse_of => :user, :dependent => :destroy has_many :domains, :inverse_of => :user, :dependent => :destroy
has_many :records, :inverse_of => :user, :dependent => :destroy has_many :records, :inverse_of => :user, :dependent => :destroy
has_many :permissions, :inverse_of => :user, :dependent => :destroy has_many :permissions, :inverse_of => :user, :dependent => :destroy
has_many :permitted_domains, :through => :permissions, :source => :domain
def name def name
"#{first_name} #{last_name}" "#{first_name} #{last_name}"
@ -27,7 +30,7 @@ class User < ActiveRecord::Base
delegate :can?, :cannot?, :to => :ability delegate :can?, :cannot?, :to => :ability
def ability def ability
@ability ||= Ability.new(self) @ability ||= Ability.new(:user => self)
end end
end end

5
config/initializers/cancan.rb

@ -1,5 +1,4 @@
# tweaks to allow Squeel Join # https://gist.github.com/1523940
module CanCan module CanCan
module ModelAdapters module ModelAdapters
@ -49,7 +48,7 @@ module CanCan
if value.kind_of? Hash if value.kind_of? Hash
reflection = model_class.reflect_on_association(name_sym) reflection = model_class.reflect_on_association(name_sym)
association_class = reflection.class_name.constantize association_class = reflection.class_name.constantize
name = reflection.table_name.to_sym name_sym = reflection.table_name.to_sym
value = tableized_conditions(value, association_class) value = tableized_conditions(value, association_class)
end end
result_hash[name_sym] = value result_hash[name_sym] = value

31
db/migrate/20120115174339_add_userstamps_to_models.rb

@ -0,0 +1,31 @@
class AddUserstampsToModels < ActiveRecord::Migration
def change
add_column :users, :created_by_id, :integer
add_column :users, :updated_by_id, :integer
execute "UPDATE users SET created_by_id = id, updated_by_id = id"
add_column :domains, :created_by_id, :integer
add_column :domains, :updated_by_id, :integer
execute "UPDATE domains SET created_by_id = user_id, updated_by_id = user_id"
add_column :records, :created_by_id, :integer
add_column :records, :updated_by_id, :integer
execute <<-SQL
UPDATE records
LEFT JOIN domains ON records.domain_id = domains.id
SET
records.created_by_id = IFNULL(records.user_id, domains.user_id),
records.updated_by_id = IFNULL(records.user_id, domains.user_id)
SQL
add_column :permissions, :created_by_id, :integer
add_column :permissions, :updated_by_id, :integer
execute <<-SQL
UPDATE permissions
LEFT JOIN domains ON permissions.domain_id = domains.id
SET
permissions.created_by_id = domains.user_id,
permissions.updated_by_id = domains.user_id
SQL
end
end

44
spec/models/ability_spec.rb

@ -7,39 +7,49 @@ describe Ability do
context "basic" do context "basic" do
it "allows me to manage my domains and their records, and my hosts" do it "allows me to manage my domains and their records, and my hosts" do
ability.should be_able_to(:manage, domain) user.should be_able_to(:manage, domain)
ability.should be_able_to(:manage, a_record) user.should be_able_to(:manage, a_record)
ability.should be_able_to(:manage, soa_record) user.should be_able_to(:manage, soa_record)
ability.should_not be_able_to(:delete, soa_record) # SOA deleted only via parent user.should_not be_able_to(:delete, soa_record) # SOA deleted only via parent
ability.should be_able_to(:manage, host_a_record) user.should be_able_to(:manage, host_a_record)
end end
it "denies other user to manage my domains and their records, and my hosts" do it "denies other user to manage my domains and their records, and my hosts" do
other_user_ability.should_not be_able_to(:manage, domain) other_user.should_not be_able_to(:manage, domain)
other_user_ability.should_not be_able_to(:manage, a_record) other_user.should_not be_able_to(:manage, a_record)
other_user_ability.should_not be_able_to(:manage, soa_record) other_user.should_not be_able_to(:manage, soa_record)
other_user_ability.should_not be_able_to(:manage, host_a_record) other_user.should_not be_able_to(:manage, host_a_record)
end end
it "allows admin to manage other user's hosts" do it "allows admin to manage other user's hosts" do
admin_ability.should be_able_to(:manage, host_a_record) admin.should be_able_to(:manage, host_a_record)
end end
end end
context "permission" do context "permitted" do
it "allows other user to manage user's domains and records, if permitted" do before do
permission # ensure permission to domain permission # ensure permission to domain
other_user_ability.should be_able_to(:manage, domain)
other_user_ability.should be_able_to(:manage, a_record)
other_user_ability.should be_able_to(:manage, soa_record)
end end
it "allows other user to manage user's domains and records, if permitted" do
other_user.should be_able_to(:manage, domain)
other_user.should be_able_to(:manage, a_record)
other_user.should be_able_to(:manage, soa_record)
end
it "allows other user to manage user's permitted subdomains" do
other_user.should be_able_to(:manage, subdomain)
other_user.should be_able_to(:manage, subsubdomain)
end
end
context "permission" do
it "allows me to manage my domain's permissions" do it "allows me to manage my domain's permissions" do
ability.should be_able_to(:manage, permission) user.should be_able_to(:manage, permission)
end end
it "denies other user to manage my domain's permissions" do it "denies other user to manage my domain's permissions" do
other_user_ability.should_not be_able_to(:manage, permission) other_user.should_not be_able_to(:manage, permission)
end end
end end

18
spec/models/domain_spec.rb

@ -58,19 +58,17 @@ describe Domain do
domain.name = 'clyfe.ro' domain.name = 'clyfe.ro'
domain.should be_valid domain.should be_valid
# stub a parent domain on another user account, with no permissions present User.do_as(user) do
mock_domain = mock( # stub a parent domain on another user account, with no permissions present
:user_id => third_user.id, mock_domain = mock(:user_id => third_user.id, :user => third_user, :name => 'x')
:user => third_user, domain.stub(:parent_domain).and_return(mock_domain)
:name => 'x' domain.should have(1).errors_on(:name)
) end
Domain.stub(:find_by_name).and_return(mock_domain)
domain.should have(1).errors_on(:name)
end end
it "queries domains corectly in index" do it "queries domains corectly in index" do
wheres = Domain.accessible_by(ability).where_values wheres = Domain.accessible_by(user.ability).where_values
joins = Domain.accessible_by(ability).joins_values.map{|j| [j._name, j._type]} joins = Domain.accessible_by(user.ability).joins_values.map{|j| [j._name, j._type]}
wheres.should == ["(`permissions`.`user_id` = #{user.id}) OR (`domains`.`user_id` = #{user.id})"] wheres.should == ["(`permissions`.`user_id` = #{user.id}) OR (`domains`.`user_id` = #{user.id})"]
joins.should == [[:permissions, Arel::Nodes::OuterJoin]] joins.should == [[:permissions, Arel::Nodes::OuterJoin]]
end end

8
spec/models/record_spec.rb

@ -28,15 +28,15 @@ describe Record do
end end
it "queries records corectly in index" do it "queries records corectly in index" do
wheres = Record.accessible_by(ability).where_values wheres = Record.accessible_by(user.ability).where_values
joins = Record.accessible_by(ability).joins_values joins = Record.accessible_by(user.ability).joins_values
wheres.should == ["(`permissions`.`user_id` = #{user.id}) OR (`domains`.`user_id` = #{user.id})"] wheres.should == ["(`permissions`.`user_id` = #{user.id}) OR (`domains`.`user_id` = #{user.id})"]
record_joins_expectations(joins) record_joins_expectations(joins)
end end
it "queries A records corectly in index" do it "queries A records corectly in index" do
wheres = A.accessible_by(ability).where_values wheres = A.accessible_by(user.ability).where_values
joins = A.accessible_by(ability).joins_values joins = A.accessible_by(user.ability).joins_values
wheres.size.should == 2 wheres.size.should == 2
wheres.second.should == "(`permissions`.`user_id` = #{user.id}) OR ((`records`.`user_id` = #{user.id}) OR (`domains`.`user_id` = #{user.id}))" wheres.second.should == "(`permissions`.`user_id` = #{user.id}) OR ((`records`.`user_id` = #{user.id}) OR (`domains`.`user_id` = #{user.id}))"
record_joins_expectations(joins) record_joins_expectations(joins)

41
spec/support/shared_context/data.rb

@ -1,44 +1,23 @@
shared_context "data" do shared_context "data" do
let(:admin){create(:user)} # admin is a user that owns host domains
let(:user){create(:user)} let(:user){create(:user)} # a regular user
let(:ability){Ability.new(:user => user)}
let(:other_user){create(:user)} let(:other_user){create(:user)}
let(:other_user_ability){Ability.new(:user => other_user)}
let(:third_user){create(:user)} let(:third_user){create(:user)}
let(:third_user_ability){Ability.new(:user => third_user)}
let(:domain){ def make_domain(options)
domain = build(:domain, :user => user) domain = build(:domain, options)
domain.setup(FactoryGirl.generate(:email)) domain.setup(FactoryGirl.generate(:email))
domain.save! domain.save!
domain.soa_record.update_serial! domain.soa_record.update_serial!
domain domain
} end
let(:domain){make_domain(:user => user)}
let(:subdomain){make_domain(:name => "sub.#{domain.name}", :user => user)}
let(:subsubdomain){make_domain(:name => "sub.#{subdomain.name}", :user => user)}
let(:host_domain){make_domain(:user => admin, :name => Settings.host_domains.first)}
let(:a_record){create(:a, :content => '127.0.0.1', :domain => domain)} let(:a_record){create(:a, :content => '127.0.0.1', :domain => domain)}
let(:soa_record){domain.soa_record} let(:soa_record){domain.soa_record}
# admin is a user that owns host domains
let(:admin){
admin_user = create(:user,
:first_name => 'admin',
:last_name => 'admin',
:email => 'admin@entrydns.net',
:confirmed_at => Time.now
)
admin_user.confirm!
admin_user
}
let(:admin_ability){Ability.new(:user => admin)}
let(:host_domain){
domain = build(:domain, :user => admin, :name => Settings.host_domains.first)
domain.setup(FactoryGirl.generate(:email))
domain.save!
domain.soa_record.update_serial!
domain
}
let(:host_a_record){create(:a, :content => '127.0.0.1', :domain => host_domain, :user => user)} let(:host_a_record){create(:a, :content => '127.0.0.1', :domain => host_domain, :user => user)}
let(:permission){create(:permission, :domain => domain, :user => other_user)} let(:permission){create(:permission, :domain => domain, :user => other_user)}

Loading…
Cancel
Save