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 'squeel', '~> 0.9.3'
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 'pjax_rails', '~> 0.1.10'
gem 'validates_hostname', '~> 1.0.0', :git => 'https://github.com/KimNorgaard/validates_hostname.git'

35
Gemfile.lock

@ -6,9 +6,9 @@ GIT
GIT
remote: https://github.com/activescaffold/active_scaffold.git
revision: 7ece16b812891b155818ba466c4490b631bccffb
revision: 81a0db7cd2be4405ee097e59fde3dd5efb4ae66b
specs:
active_scaffold (3.1.13)
active_scaffold (3.1.14)
rails (~> 3.1.0)
GEM
@ -62,7 +62,7 @@ GEM
rack-test (>= 0.5.4)
selenium-webdriver (~> 2.0)
xpath (~> 0.1.4)
childprocess (0.2.4)
childprocess (0.3.0)
ffi (~> 1.0.6)
chunky_png (1.2.5)
coffee-rails (3.1.1)
@ -72,7 +72,7 @@ GEM
coffee-script-source
execjs
coffee-script-source (1.2.0)
compass (0.12.alpha.3)
compass (0.12.alpha.4)
chunky_png (~> 1.2)
fssm (>= 0.2.7)
sass (~> 3.1)
@ -87,17 +87,17 @@ GEM
erubis (2.7.0)
execjs (1.2.13)
multi_json (~> 1.0)
factory_girl (2.3.2)
factory_girl (2.4.0)
activesupport
factory_girl_rails (1.4.0)
factory_girl (~> 2.3.0)
factory_girl_rails (1.5.0)
factory_girl (~> 2.4.0)
railties (>= 3.0.0)
faker (1.0.1)
i18n (~> 0.4)
ffi (1.0.11)
fssm (0.2.7)
fssm (0.2.8.1)
gem_plugin (0.2.3)
guard (0.9.4)
guard (0.10.0)
ffi (>= 0.5.0)
thor (~> 0.14.6)
guard-rspec (0.4.5)
@ -111,7 +111,7 @@ GEM
jquery-rails (1.0.19)
railties (~> 3.0)
thor (~> 0.14)
json (1.6.4)
json (1.6.5)
libnotify (0.5.9)
libv8 (3.3.10.4)
mail (2.3.0)
@ -129,20 +129,20 @@ GEM
net-ssh (>= 1.99.1)
net-sftp (2.0.5)
net-ssh (>= 2.0.9)
net-ssh (2.2.1)
net-ssh (2.3.0)
net-ssh-gateway (1.1.0)
net-ssh (>= 1.99.1)
nilify_blanks (1.0.0)
activerecord (>= 3.0.0)
activesupport (>= 3.0.0)
nokogiri (1.5.0)
orm_adapter (0.0.5)
orm_adapter (0.0.6)
pjax_rails (0.1.10)
jquery-rails
polyamorous (0.5.0)
activerecord (~> 3.0)
polyglot (0.3.3)
rack (1.3.5)
rack (1.3.6)
rack-cache (1.1)
rack (>= 0.4)
rack-mount (0.8.3)
@ -194,8 +194,8 @@ GEM
railties (~> 3.1.0)
sass (~> 3.1.10)
tilt (~> 1.3.2)
selenium-webdriver (2.15.0)
childprocess (>= 0.2.1)
selenium-webdriver (2.16.0)
childprocess (>= 0.2.5)
ffi (~> 1.0.9)
multi_json (~> 1.0.4)
rubyzip
@ -221,9 +221,11 @@ GEM
polyglot
polyglot (>= 0.3.1)
tzinfo (0.3.31)
uglifier (1.2.1)
uglifier (1.2.2)
execjs (>= 0.3.0)
multi_json (>= 1.0.2)
userstamp_basic (0.1.0)
sentient_user (>= 0.1.0)
warden (1.0.6)
rack (>= 1.0)
xpath (0.1.4)
@ -266,4 +268,5 @@ DEPENDENCIES
squeel (~> 0.9.3)
therubyracer
uglifier
userstamp_basic (~> 0.1.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, 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
# 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
set_inheritance_column "sti_disabled"
nilify_blanks
stampable
# optional IP for create form, results in a type A record
attr_accessor :ip
@ -8,6 +9,7 @@ class Domain < ActiveRecord::Base
belongs_to :user, :inverse_of => :domain
has_many :records, :inverse_of => :domain, :dependent => :destroy
has_many :permissions, :inverse_of => :domain, :dependent => :destroy
has_many :permitted_users, :through => :permissions, :source => :user
cattr_reader :types
@@types = ['NATIVE', 'MASTER', 'SLAVE', 'SUPERSLAVE']
@ -49,8 +51,9 @@ class Domain < ActiveRecord::Base
# non-TLD validation
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.present? && user.cannot?(:manage, parent_domain)
# If parent domain is on our system, the user be permitted to manage current 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"
end
end
@ -69,6 +72,12 @@ class Domain < ActiveRecord::Base
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
before_create do
@ -116,6 +125,10 @@ class Domain < ActiveRecord::Base
scope :host_domains, where(:name => Settings.host_domains)
def subdomains
Domain.where(:name_reversed.matches => "#{name_reversed}.%")
end
def host_domain?
Settings.host_domains.include?(name)
end

2
app/models/permission.rb

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

2
app/models/record.rb

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

5
app/models/user.rb

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

5
config/initializers/cancan.rb

@ -1,5 +1,4 @@
# tweaks to allow Squeel Join
# https://gist.github.com/1523940
module CanCan
module ModelAdapters
@ -49,7 +48,7 @@ module CanCan
if value.kind_of? Hash
reflection = model_class.reflect_on_association(name_sym)
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)
end
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
it "allows me to manage my domains and their records, and my hosts" do
ability.should be_able_to(:manage, domain)
ability.should be_able_to(:manage, a_record)
ability.should be_able_to(:manage, soa_record)
ability.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, domain)
user.should be_able_to(:manage, a_record)
user.should be_able_to(:manage, soa_record)
user.should_not be_able_to(:delete, soa_record) # SOA deleted only via parent
user.should be_able_to(:manage, host_a_record)
end
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_ability.should_not be_able_to(:manage, a_record)
other_user_ability.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, domain)
other_user.should_not be_able_to(:manage, a_record)
other_user.should_not be_able_to(:manage, soa_record)
other_user.should_not be_able_to(:manage, host_a_record)
end
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
context "permission" do
it "allows other user to manage user's domains and records, if permitted" do
context "permitted" do
before do
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
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
ability.should be_able_to(:manage, permission)
user.should be_able_to(:manage, permission)
end
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

18
spec/models/domain_spec.rb

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

8
spec/models/record_spec.rb

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

41
spec/support/shared_context/data.rb

@ -1,44 +1,23 @@
shared_context "data" do
let(:user){create(:user)}
let(:ability){Ability.new(:user => user)}
let(:admin){create(:user)} # admin is a user that owns host domains
let(:user){create(:user)} # a regular user
let(:other_user){create(:user)}
let(:other_user_ability){Ability.new(:user => other_user)}
let(:third_user){create(:user)}
let(:third_user_ability){Ability.new(:user => third_user)}
let(:domain){
domain = build(:domain, :user => user)
def make_domain(options)
domain = build(:domain, options)
domain.setup(FactoryGirl.generate(:email))
domain.save!
domain.soa_record.update_serial!
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(: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(:permission){create(:permission, :domain => domain, :user => other_user)}

Loading…
Cancel
Save