tuersteher 0.5.2 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -2,3 +2,4 @@ pkg
2
2
  .idea
3
3
  tuersteher*.gem
4
4
  nbproject
5
+ .rvmrc
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.2
1
+ 0.6.0
data/lib/tuersteher.rb CHANGED
@@ -7,6 +7,7 @@
7
7
  #
8
8
 
9
9
  require 'singleton'
10
+ require 'logger'
10
11
 
11
12
  module Tuersteher
12
13
 
@@ -143,7 +144,10 @@ module Tuersteher
143
144
  Tuersteher::TLogger.logger.info "extend_path_rules_with_prefix: #{prefix}"
144
145
  @path_prefix = prefix
145
146
  path_rules.each do |rule|
146
- rule.path = "#{prefix}#{rule.path}" unless rule.path == :all
147
+ path_spec = rule.path_spezification
148
+ if path_spec
149
+ path_spec.path = "#{prefix}#{path_spec.path}"
150
+ end
147
151
  end
148
152
  end
149
153
 
@@ -302,7 +306,7 @@ module Tuersteher
302
306
  url_path = request.send(@@url_path_method)
303
307
  unless path_access?(url_path, req_method)
304
308
  usr_id = current_user && current_user.respond_to?(:id) ? current_user.id : current_user.object_id
305
- msg = "Tuersteher#check_access: access denied for #{request.request_uri} :#{req_method} user.id=#{usr_id}"
309
+ msg = "Tuersteher#check_access: access denied for #{url_path} :#{req_method} user.id=#{usr_id}"
306
310
  Tuersteher::TLogger.logger.warn msg
307
311
  logger.warn msg # log message also for Rails-Default logger
308
312
  access_denied # Methode aus dem authenticated_system, welche ein redirect zum login auslöst
@@ -360,25 +364,148 @@ module Tuersteher
360
364
  end # of module ModelExtensions
361
365
 
362
366
 
367
+ # The Classes for the separate Rule-Specifications
368
+ class PathSpecification
369
+ attr_reader :path
370
+
371
+ def initialize path, negation
372
+ @negation = negation
373
+ self.path = path
374
+ end
375
+
376
+ def path= url_path
377
+ @path = url_path
378
+ # url_path in regex ^#{path} wandeln ausser bei "/",
379
+ # dies darf keine Regex mit ^/ werden, da diese dann ja immer matchen wuerde
380
+ if url_path == "/"
381
+ @path_regex = /^\/$/
382
+ else
383
+ @path_regex = /^#{url_path}/
384
+ end
385
+ end
386
+
387
+ def grant? path_or_model, method, login_ctx
388
+ rc = @path_regex =~ path_or_model
389
+ rc = !rc if @negation
390
+ rc
391
+ end
392
+ end
393
+
394
+ class ModelSpecification
395
+ def initialize clazz, negation
396
+ @clazz, @negation = clazz, negation
397
+ end
398
+
399
+ def grant? path_or_model, method, login_ctx
400
+ m_class = path_or_model.instance_of?(Class) ? path_or_model : path_or_model.class
401
+ rc = @clazz == m_class
402
+ rc = !rc if @negation
403
+ rc
404
+ end
405
+ end
406
+
407
+ class RolesSpecification
408
+ attr_reader :roles, :negation
409
+
410
+ def initialize role, negation
411
+ @negation = negation
412
+ @roles = [role]
413
+ end
414
+
415
+ def grant? path_or_model, method, login_ctx
416
+ return false if login_ctx.nil?
417
+ # roles sind or verknüpft
418
+ rc = @roles.any?{|role| login_ctx.has_role?(role) }
419
+ rc = !rc if @negation
420
+ rc
421
+ end
422
+ end
423
+
424
+ class MethodSpecification
425
+ def initialize method, negation
426
+ @method, @negation = method, negation
427
+ end
428
+
429
+ def grant? path_or_model, method, login_ctx
430
+ rc = @method==method
431
+ rc = !rc if @negation
432
+ rc
433
+ end
434
+ end
435
+
436
+ class ExtensionSpecification
437
+ def initialize method_name, negation, expected_value=nil
438
+ @method, @negation, @expected_value = method_name, negation, expected_value
439
+ end
440
+
441
+ def grant? path_or_model, method, login_ctx
442
+ rc = false
443
+ if path_or_model.is_a?(String)
444
+ # path-variante
445
+ return false if login_ctx.nil?
446
+ unless login_ctx.respond_to?(@method)
447
+ Tuersteher::TLogger.logger.warn("#{to_s}.grant? => false why Login-Context have not method '#{@method}'!")
448
+ return false
449
+ end
450
+ if @expected_value
451
+ rc = login_ctx.send(@method, @expected_value)
452
+ else
453
+ rc = login_ctx.send(@method)
454
+ end
455
+ else
456
+ # model-variante
457
+ unless path_or_model.respond_to?(@method)
458
+ m_msg = path_or_model.instance_of?(Class) ? "Class '#{path_or_model.name}'" : "Object '#{path_or_model.class}'"
459
+ Tuersteher::TLogger.logger.warn("#{to_s}.grant? => false why #{m_msg} have not method '#{@method}'!")
460
+ return false
461
+ end
462
+ if @expected_value
463
+ rc = path_or_model.send(@method, login_ctx, @expected_value)
464
+ else
465
+ rc = path_or_model.send(@method, login_ctx)
466
+ end
467
+ end
468
+ rc = !rc if @negation
469
+ rc
470
+ end
471
+ end
472
+
473
+
363
474
 
364
- # Astracte base class for Access-Rules
475
+ # Abstracte base class for Access-Rules
365
476
  class BaseAccessRule
477
+ attr_reader :rule_spezifications
366
478
 
367
479
  def initialize
368
- @roles = []
369
- @access_method = :all
480
+ @rule_spezifications = []
481
+ @last_role_specification
370
482
  end
371
483
 
372
484
  # add role
373
485
  def role(role_name)
486
+ return self if role_name==:all # :all is only syntax sugar
374
487
  raise "wrong role '#{role_name}'! Must be a symbol " unless role_name.is_a?(Symbol)
375
- @roles << role_name
488
+ # roles are OR-linked (per default)
489
+ # => add the role to RolesSpecification, create only new RolesSpecification if not exist
490
+ if @last_role_specification
491
+ raise("Mixin of role and not.role are yet not implemented!") if @negation != @last_role_specification.negation
492
+ @last_role_specification.roles << role_name
493
+ else
494
+ @last_role_specification = RolesSpecification.new(role_name, @negation)
495
+ @rule_spezifications << @last_role_specification
496
+ end
497
+ @negation = false if @negation
376
498
  self
377
499
  end
378
500
 
379
501
  # add list of roles
380
502
  def roles(*role_names)
381
- role_names.flatten.each{|role_name| role(role_name)}
503
+ negation_state = @negation
504
+ role_names.flatten.each do |role_name|
505
+ self.role(role_name)
506
+ @negation = negation_state # keep Negation-State for all roles
507
+ end
508
+ @negation = false if @negation
382
509
  self
383
510
  end
384
511
 
@@ -387,11 +514,21 @@ module Tuersteher
387
514
  # method_name: Symbol with the name of the method to call for addional check
388
515
  # expected_value: optional expected value for the result of the with metho_name specified method, defalt is true
389
516
  def extension method_name, expected_value=nil
390
- @check_extensions ||= {}
391
- @check_extensions[method_name] = expected_value
517
+ @rule_spezifications << ExtensionSpecification.new(method_name, @negation, expected_value)
518
+ @negation = false if @negation
392
519
  self
393
520
  end
394
521
 
522
+ # set methode for access
523
+ # access_method Name of Methode for access as Symbol
524
+ def method(access_method)
525
+ return self if access_method==:all # :all is only syntax sugar
526
+ @rule_spezifications << MethodSpecification.new(access_method, @negation)
527
+ @negation = false if @negation
528
+ self
529
+ end
530
+
531
+
395
532
  # mark this rule as grant-rule
396
533
  def grant
397
534
  self
@@ -408,43 +545,34 @@ module Tuersteher
408
545
  @deny
409
546
  end
410
547
 
411
- # set methode for access
412
- # access_method Name of Methode for access as Symbol
413
- def method(access_method)
414
- @access_method = access_method
415
- self
416
- end
417
548
 
418
- # negate role-membership
549
+ # negate role followed rule specification (role or extension
419
550
  def not
420
- @not = true
551
+ @negation = true
421
552
  self
422
553
  end
423
554
 
424
- protected
425
-
426
- # check, if this rule granted for specified user
427
- def grant_role? user
428
- return true if @roles.empty?
429
- return false if user.nil?
430
- role = @roles.detect{|r| user.has_role?(r)}
431
- role = !role if @not
432
- return true if role
433
- false
555
+ # check, if this rule fired for specified parameter
556
+ def fired? path_or_model, method, login_ctx
557
+ login_ctx = nil if login_ctx==:false # manche Authenticate-System setzen den login_ctx/user auf :false
558
+ @rule_spezifications.all?{|spec| spec.grant?(path_or_model, method, login_ctx)}
434
559
  end
435
560
 
436
- def grant_access_method? method
437
- return true if @access_method==:all
438
- @access_method == method
439
- end
440
561
 
562
+ def to_s
563
+ s = "#{self.class}["
564
+ s << 'DENY ' if @deny
565
+ s << @rule_spezifications.map(&:to_s).join(', ')
566
+ s << ']'
567
+ s
568
+ end
441
569
  end # of BaseAccessRule
442
570
 
443
571
 
444
572
  class PathAccessRule < BaseAccessRule
445
573
 
446
574
  METHOD_NAMES = [:get, :edit, :put, :delete, :post, :all].freeze
447
- attr_reader :path
575
+ attr_reader :path_spezification
448
576
 
449
577
  # Zugriffsregel
450
578
  #
@@ -453,23 +581,13 @@ module Tuersteher
453
581
  def initialize(path)
454
582
  raise "wrong path '#{path}'! Must be a String or :all ." unless path==:all or path.is_a?(String)
455
583
  super()
456
- self.path = path
457
- end
458
-
459
- def path= url_path
460
- @path = url_path
461
- if url_path != :all
462
- # path in regex ^#{path} wandeln ausser bei "/",
463
- # dies darf keine Regex mit ^/ werden,
464
- # da diese ja immer matchen wuerde
465
- if url_path == "/"
466
- @path_regex = /^\/$/
467
- else
468
- @path_regex = /^#{url_path}/
469
- end
584
+ if path != :all # :all is only syntax sugar
585
+ @path_spezification = PathSpecification.new(path, @negation)
586
+ @rule_spezifications << @path_spezification
470
587
  end
471
588
  end
472
589
 
590
+
473
591
  # set http-methode
474
592
  # http_method http-Method, allowed is :get, :put, :delete, :post, :all
475
593
  def method(http_method)
@@ -478,57 +596,6 @@ module Tuersteher
478
596
  self
479
597
  end
480
598
 
481
-
482
-
483
- # pruefen, ob Zugriff fuer angegebenen
484
- # path / method fuer den current_user erlaubt ist
485
- #
486
- # user ist ein Object (meist der Loginuser),
487
- # welcher die Methode 'has_role?(role)' besitzen muss.
488
- # *roles ist dabei eine Array aus Symbolen
489
- #
490
- def fired?(path, method, user)
491
- user = nil if user==:false # manche Authenticate-System setzen den user auf :false
492
-
493
- if @path!=:all && !(@path_regex =~ path)
494
- return false
495
- end
496
-
497
- return false unless grant_access_method?(method)
498
- return false unless grant_role?(user)
499
- return false unless grant_extension?(user)
500
-
501
- true
502
- end
503
-
504
-
505
- def to_s
506
- s = "PathAccesRule[#{@deny ? 'DENY ' : ''}#{@path}, #{@access_method}, #{@roles.join(' ')}"
507
- s << " #{@check_extensions.inspect}" if @check_extensions
508
- s << ']'
509
- s
510
- end
511
-
512
- private
513
-
514
- # check, if this rule grant the defined extension (if exist)
515
- def grant_extension? user
516
- return true if @check_extensions.nil?
517
- return false if user.nil? # check_extensions need a user
518
- @check_extensions.each do |key, value|
519
- unless user.respond_to?(key)
520
- Tuersteher::TLogger.logger.warn("#{to_s}.fired? => false why user have not check-extension method '#{key}'!")
521
- return false
522
- end
523
- if value
524
- return false unless user.send(key,value)
525
- else
526
- return false unless user.send(key)
527
- end
528
- end
529
- true
530
- end
531
-
532
599
  end
533
600
 
534
601
 
@@ -538,74 +605,13 @@ module Tuersteher
538
605
  # erzeugt neue Object-Zugriffsregel
539
606
  #
540
607
  # clazz Model-Klassenname oder :all fuer alle
541
- # access_type Zugriffsart (:create, :update, :destroy, :all o.A. selbst definierte Typem)
542
- # roles Aufzählung der erforderliche Rolen (:all für ist egal),
543
- # hier ist auch ein Array von Symbolen möglich
544
- # block optionaler Block, wird mit model und user aufgerufen und muss true oder false liefern
545
- # hier ein Beispiel mit Block:
546
- # <code>
547
- # # Regel, in der sich jeder User selbst aendern darf
548
- # ModelAccessRule.new(User, :update, :all){|model,user| model.id==user.id}
549
- # </code>
550
608
  #
551
609
  def initialize(clazz)
552
610
  raise "wrong clazz '#{clazz}'! Must be a Class or :all ." unless clazz==:all or clazz.is_a?(Class)
553
611
  super()
554
- @clazz = clazz.instance_of?(Symbol) ? clazz : clazz.to_s
555
- end
556
-
557
-
558
- # liefert true, wenn zugriff fuer das angegebene model mit
559
- # der Zugriffsart perm für das security_object hat
560
- #
561
- # model des zupruefende ModelObject
562
- # perm gewunschte Zugriffsart (Symbol :create, :update, :destroy)
563
- #
564
- # user ist ein User-Object (meist der Loginuser),
565
- # welcher die Methode 'has_role?(*roles)' besitzen muss.
566
- # *roles ist dabei eine Array aus Symbolen
567
- #
568
- #
569
- def fired? model, access_method, user
570
- user = nil if user==:false # manche Authenticate-System setzen den user auf :false
571
- m_class = model.instance_of?(Class) ? model : model.class
572
- if @clazz!=m_class.to_s && @clazz!=:all
573
- #Tuersteher::TLogger.logger.debug("#{to_s}.has_access? => false why #{@clazz}!=#{model.class.to_s} && #{@clazz}!=:all")
574
- return false
575
- end
576
-
577
- return false unless grant_access_method?(access_method)
578
- return false unless grant_role?(user)
579
- return false unless grant_extension?(user, model)
580
- true
581
- end
582
-
583
- def to_s
584
- s = "ModelAccessRule[#{@deny ? 'DENY ' : ''}#{@clazz}, #{@access_method}, #{@roles.join(' ')}"
585
- s << " #{@check_extensions.inspect}" if @check_extensions
586
- s << ']'
587
- s
588
- end
589
-
590
- private
591
-
592
- # check, if this rule grant the defined extension (if exist)
593
- def grant_extension? user, model
594
- return true if @check_extensions.nil?
595
- return false if model.nil? # check_extensions need a model
596
- @check_extensions.each do |key, value|
597
- unless model.respond_to?(key)
598
- m_msg = model.instance_of?(Class) ? "Class '#{model.name}'" : "Object '#{model.class}'"
599
- Tuersteher::TLogger.logger.warn("#{to_s}.fired? => false why #{m_msg} have not check-extension method '#{key}'!")
600
- return false
601
- end
602
- if value
603
- return false unless model.send(key,user,value)
604
- else
605
- return false unless model.send(key,user)
606
- end
612
+ if clazz != :all # :all is only syntax sugar
613
+ @rule_spezifications << ModelSpecification.new(clazz, @negation)
607
614
  end
608
- true
609
615
  end
610
616
 
611
617
  end
@@ -58,8 +58,8 @@ end
58
58
  @path_rules = AccessRulesStorage.instance.path_rules
59
59
  end
60
60
 
61
- specify{ @path_rules.first.path.should == :all }
62
- specify{ @path_rules.last.path.should == '/test/special' }
61
+ specify{ @path_rules.first.path_spezification.should be_nil }
62
+ specify{ @path_rules.last.path_spezification.path.should == '/test/special' }
63
63
 
64
64
  end
65
65
  end
@@ -177,7 +177,7 @@ module Tuersteher
177
177
  context 'purge_collection' do
178
178
 
179
179
  class SampleModel
180
- def owner? user; false; end
180
+ def owner?(user); false; end
181
181
  end
182
182
 
183
183
  before do
@@ -150,20 +150,47 @@ module Tuersteher
150
150
  end # of context "deny" do
151
151
 
152
152
 
153
- context "with not as role prefix" do
154
- before(:all) do
155
- @rule = PathAccessRule.new('/admin').deny.not.role(:admin)
156
- @user = stub('user')
157
- end
153
+ context "with not" do
154
+ context "as prefix for role" do
155
+ before(:all) do
156
+ @rule = PathAccessRule.new('/admin').deny.not.role(:admin)
157
+ @user = stub('user')
158
+ end
158
159
 
159
- it "should not fired for user with role :admin" do
160
- @user.stub(:has_role?){|role| role==:admin}
161
- @rule.fired?("/admin", :get, @user).should_not be_true
160
+ it "should not fired for user with role :admin" do
161
+ @user.stub(:has_role?){|role| role==:admin}
162
+ @rule.fired?("/admin", :get, @user).should_not be_true
163
+ end
164
+
165
+ it "should fired for user with role :user" do
166
+ @user.stub(:has_role?){|role| role==:user}
167
+ @rule.fired?("/admin", :get, @user).should be_true
168
+ end
162
169
  end
163
170
 
164
- it "should fired for user with role :user" do
165
- @user.stub(:has_role?){|role| role==:user}
166
- @rule.fired?("/admin", :get, @user).should be_true
171
+ context "as prefix for extension" do
172
+ before(:all) do
173
+ @rule = PathAccessRule.new('/admin').grant.role(:admin).not.extension(:login_ctx_method)
174
+ @user = stub('user')
175
+ end
176
+
177
+ it "should fired for user with role :admin and false for extension" do
178
+ @user.stub(:has_role?){|role| role==:admin}
179
+ @user.should_receive(:login_ctx_method).and_return(false)
180
+ @rule.fired?("/admin", :get, @user).should be_true
181
+ end
182
+
183
+ it "should not fired for user with role :admin and true for extension" do
184
+ @user.stub(:has_role?){|role| role==:admin}
185
+ @user.should_receive(:login_ctx_method).and_return(true)
186
+ @rule.fired?("/admin", :get, @user).should_not be_true
187
+ end
188
+
189
+ it "should not fired for user with role :user" do
190
+ @user.stub(:has_role?){|role| role==:user}
191
+ @rule.fired?("/admin", :get, @user).should be_false
192
+ end
193
+
167
194
  end
168
195
  end # of context "not" do
169
196
 
data/spec/spec_helper.rb CHANGED
@@ -1,4 +1,4 @@
1
- require 'spec'
1
+ require 'rspec'
2
2
  require 'logger'
3
3
  require File.expand_path(File.dirname(__FILE__) + "/../lib/tuersteher")
4
4
 
data/tuersteher.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{tuersteher}
8
- s.version = "0.5.2"
8
+ s.version = "0.6.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Bernd Ledig"]
12
- s.date = %q{2010-11-07}
12
+ s.date = %q{2011-02-02}
13
13
  s.description = %q{Security-Layer for Rails-Application acts like a firewall.}
14
14
  s.email = %q{bernd@ledig.info}
15
15
  s.extra_rdoc_files = [
@@ -41,12 +41,12 @@ Gem::Specification.new do |s|
41
41
  s.rubygems_version = %q{1.3.7}
42
42
  s.summary = %q{Security-Layer for Rails-Application}
43
43
  s.test_files = [
44
- "spec/spec_helper.rb",
45
- "spec/model_extensions_spec.rb",
46
- "spec/access_rules_spec.rb",
47
- "spec/path_access_rule_spec.rb",
44
+ "spec/model_extensions_spec.rb",
45
+ "spec/acces_rules_storage_spec.rb",
46
+ "spec/spec_helper.rb",
48
47
  "spec/model_access_rule_spec.rb",
49
- "spec/acces_rules_storage_spec.rb"
48
+ "spec/access_rules_spec.rb",
49
+ "spec/path_access_rule_spec.rb"
50
50
  ]
51
51
 
52
52
  if s.respond_to? :specification_version then
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tuersteher
3
3
  version: !ruby/object:Gem::Version
4
- hash: 15
4
+ hash: 7
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 5
9
- - 2
10
- version: 0.5.2
8
+ - 6
9
+ - 0
10
+ version: 0.6.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Bernd Ledig
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-11-07 00:00:00 +01:00
18
+ date: 2011-02-02 00:00:00 +01:00
19
19
  default_executable:
20
20
  dependencies: []
21
21
 
@@ -81,9 +81,9 @@ signing_key:
81
81
  specification_version: 3
82
82
  summary: Security-Layer for Rails-Application
83
83
  test_files:
84
- - spec/spec_helper.rb
85
84
  - spec/model_extensions_spec.rb
85
+ - spec/acces_rules_storage_spec.rb
86
+ - spec/spec_helper.rb
87
+ - spec/model_access_rule_spec.rb
86
88
  - spec/access_rules_spec.rb
87
89
  - spec/path_access_rule_spec.rb
88
- - spec/model_access_rule_spec.rb
89
- - spec/acces_rules_storage_spec.rb