yus 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/yus/entity.rb ADDED
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env ruby
2
+ # Entity -- yus -- 29.05.2006 -- hwyss@ywesee.com
3
+
4
+ require 'yus/privilege'
5
+
6
+ module Yus
7
+ class YusError < RuntimeError; end
8
+ class AuthenticationError < YusError; end
9
+ class CircularAffiliationError < YusError; end
10
+ class DuplicateNameError < YusError; end
11
+ class NotPrivilegedError < YusError; end
12
+ class UnknownEntityError < YusError; end
13
+ class Entity
14
+ attr_reader :name, :affiliations, :privileges
15
+ attr_accessor :valid_from, :valid_until
16
+ attr_writer :passhash
17
+ def initialize(name, valid_until=nil, valid_from=Time.now)
18
+ @name = name.to_s
19
+ @valid_until = valid_until
20
+ @valid_from = valid_from
21
+ @affiliations = []
22
+ @privileges = Hash.new(false)
23
+ @preferences = {}
24
+ @last_logins = {}
25
+ @tokens = {}
26
+ end
27
+ def allowed?(action, item=:everything)
28
+ valid? && privileged?(action, item) \
29
+ || @affiliations.any? { |entity| entity.allowed?(action, item) }
30
+ end
31
+ def authenticate(passhash)
32
+ @passhash == passhash.to_s
33
+ end
34
+ def authenticate_token(token)
35
+ if expires = (@tokens ||= {}).delete(token)
36
+ expires > Time.now
37
+ else
38
+ # Hacking-Attempt? Remove all Tokens for this user.
39
+ @tokens.clear
40
+ false
41
+ end
42
+ end
43
+ def detect_circular_affiliation(entity)
44
+ _detect_circular_affiliation(entity)
45
+ rescue CircularAffiliationError => error
46
+ error.message << ' <- "' << entity.name << '"'
47
+ raise
48
+ end
49
+ def grant(action, item=:everything, expires=:never)
50
+ action = Entity.sanitize(action)
51
+ (@privileges[action] ||= Privilege.new).grant(item, expires)
52
+ end
53
+ def info(recursive=false)
54
+ info = [ @name ]
55
+ @privileges.sort.each { |action, priv|
56
+ info.push [action, priv.info]
57
+ }
58
+ if recursive
59
+ @affiliations.sort_by { |entity| entity.name }.each { |entity|
60
+ info.push entity.info(true)
61
+ }
62
+ elsif !@affiliations.empty?
63
+ info.push @affiliations.collect { |entity| entity.name }.sort
64
+ end
65
+ info
66
+ end
67
+ def join(party)
68
+ unless(@affiliations.include?(party))
69
+ party.detect_circular_affiliation(self)
70
+ @affiliations.push(party)
71
+ end
72
+ end
73
+ def last_login(domain)
74
+ (@last_logins ||= {})[domain]
75
+ end
76
+ def leave(party)
77
+ @affiliations.delete(party)
78
+ end
79
+ def login(domain)
80
+ (@last_logins ||= {}).store(domain, Time.now)
81
+ end
82
+ def get_preference(key, domain='global')
83
+ domain_preferences(domain)[key] || domain_preferences('global')[key]
84
+ end
85
+ def privileged?(action, item=:everything)
86
+ (privilege = @privileges[Entity.sanitize(action)]) \
87
+ && privilege.granted?(item)
88
+ end
89
+ def privileged_until(action, item=:everything)
90
+ if(privilege = @privileges[Entity.sanitize(action)])
91
+ privilege.expiry_time(item)
92
+ else
93
+ raise NotPrivilegedError
94
+ end
95
+ end
96
+ def remove_token(token)
97
+ @tokens.delete(token) if @tokens
98
+ end
99
+ def rename(new_name)
100
+ @name = new_name
101
+ end
102
+ def revoke(action, item=:everything, time=nil)
103
+ action = Entity.sanitize(action)
104
+ if(priv = @privileges[action])
105
+ priv.revoke(item, time)
106
+ end
107
+ end
108
+ def set_preference(key, value, domain=nil)
109
+ domain_preferences(domain || 'global')[key] = value
110
+ end
111
+ def set_token(token, expires)
112
+ (@tokens ||= {}).store token, expires
113
+ end
114
+ def to_s
115
+ @name
116
+ end
117
+ def valid?
118
+ now = Time.now
119
+ @valid_from < now && (!@valid_until || @valid_until > now)
120
+ end
121
+ def Entity.sanitize(term)
122
+ term.to_s.downcase
123
+ end
124
+ protected
125
+ def _detect_circular_affiliation(entity)
126
+ if(@affiliations.include?(entity))
127
+ raise CircularAffiliationError,
128
+ "circular affiliation detected: \"#{entity.name}\""
129
+ end
130
+ @affiliations.each { |aff| aff._detect_circular_affiliation(entity) }
131
+ rescue CircularAffiliationError => error
132
+ error.message << ' <- "' << @name << '"'
133
+ raise
134
+ end
135
+ private
136
+ def domain_preferences(domain)
137
+ @preferences[domain] ||= {}
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env ruby
2
+ # Persistence::ODBA -- yus -- 31.05.2006 -- hwyss@ywesee.com
3
+
4
+ require 'odba'
5
+ require 'odba/drbwrapper'
6
+ require 'yus/entity'
7
+ require 'yus/server'
8
+ require 'yus/session'
9
+
10
+ module Yus
11
+ module Persistence
12
+ class Odba
13
+ def initialize
14
+ @entities = ODBA.cache.fetch_named('entities', self) { Hash.new }
15
+ end
16
+ def add_entity(entity)
17
+ @entities.store(Entity.sanitize(entity.name), entity)
18
+ entity.odba_store
19
+ @entities.odba_store
20
+ entity
21
+ end
22
+ def delete_entity(name)
23
+ if entity = @entities.delete(Entity.sanitize(name))
24
+ @entities.odba_store
25
+ affiliations = entity.affiliations
26
+ affiliations.odba_delete unless affiliations.odba_unsaved?
27
+ entity.odba_delete
28
+ entity
29
+ end
30
+ end
31
+ def entities
32
+ @entities.values
33
+ end
34
+ def find_entity(name)
35
+ @entities[Entity.sanitize(name)]
36
+ end
37
+ def save_entity(entity)
38
+ if(@entities[entity.name])
39
+ entity.odba_store
40
+ else
41
+ @entities.delete_if { |name, ent| ent.name == entity.name }
42
+ add_entity(entity)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ class Entity
48
+ include ODBA::Persistable
49
+ ODBA_SERIALIZABLE = ['@last_logins', '@privileges', '@preferences', '@tokens']
50
+ alias :odba_join :join
51
+ alias :odba_leave :leave
52
+ def join(entity)
53
+ res = odba_join(entity)
54
+ @affiliations.odba_store
55
+ res
56
+ end
57
+ def leave(entity)
58
+ res = odba_leave(entity)
59
+ @affiliations.odba_store
60
+ res
61
+ end
62
+ end
63
+ class Server
64
+ alias :odba_login_entity :login_entity
65
+ alias :odba_login_root :login_root
66
+ def login_entity(*args)
67
+ if(entity = odba_login_entity(*args))
68
+ ODBA::DRbWrapper.new(entity)
69
+ end
70
+ end
71
+ def login_root(*args)
72
+ if(root = odba_login_root(*args))
73
+ ODBA::DRbWrapper.new(root)
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+ # Persistence::Og -- yus -- 31.05.2006 -- hwyss@ywesee.com
3
+
4
+ require 'og'
5
+ require 'yus/entity'
6
+ require 'yus/server'
7
+
8
+ module Yus
9
+ module Persistence
10
+ class Og
11
+ def add_entity(entity)
12
+ entity.save
13
+ end
14
+ def entities
15
+ Entity.find_all
16
+ end
17
+ def find_entity(name)
18
+ Entity.find_by_name(name)
19
+ end
20
+ def save_entity(entity)
21
+ entity.save
22
+ end
23
+ end
24
+ end
25
+ class Privilege
26
+ property :expiry_time, Time
27
+ property :items, Hash
28
+ end
29
+ class Entity
30
+ property :name, String
31
+ property :valid_from, Time
32
+ property :valid_until, Time
33
+ property :preferences, Hash
34
+ has_many :affiliations, Entity
35
+ has_many :privileges, Privilege
36
+ end
37
+ end
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env ruby
2
+ # Privilege -- yus -- 31.05.2006 -- hwyss@ywesee.com
3
+
4
+ module Yus
5
+ class Privilege
6
+ attr_writer :expiry_time
7
+ def initialize
8
+ @items = {}
9
+ end
10
+ def expiry_time(item=:everything)
11
+ if(time = [@items[item], @items[:everything]].compact.max)
12
+ time if time.is_a?(Time)
13
+ else
14
+ raise NotPrivilegedError
15
+ end
16
+ end
17
+ def grant(item, expiry_time=:never)
18
+ @items.store(item, expiry_time)
19
+ end
20
+ def granted?(item)
21
+ if(expiry_time = @items[item])
22
+ case expiry_time
23
+ when Time
24
+ Time.now < expiry_time
25
+ else
26
+ true
27
+ end
28
+ elsif(@items.include?(:everything))
29
+ # check time
30
+ granted?(:everything)
31
+ else
32
+ item = item.to_s.dup
33
+ if(item[-1] != ?*)
34
+ while(!item.empty?)
35
+ item.slice!(/[^.]*$/)
36
+ if(granted?(item + "*"))
37
+ return true
38
+ end
39
+ item.chop!
40
+ end
41
+ end
42
+ false
43
+ end
44
+ end
45
+ def info
46
+ @items.collect { |item, time|
47
+ info = [item.to_s]
48
+ if time.is_a?(Time)
49
+ info.push time
50
+ end
51
+ info
52
+ }.sort
53
+ end
54
+ def revoke(item, expiry_time=nil)
55
+ case expiry_time
56
+ when Time
57
+ @items.store(item, expiry_time)
58
+ else
59
+ @items.delete(item)
60
+ end
61
+ end
62
+ end
63
+ end
data/lib/yus/server.rb ADDED
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env ruby
2
+ # Server -- yus -- 31.05.2006 -- hwyss@ywesee.com
3
+
4
+ require 'drb'
5
+ require 'yus/entity'
6
+ require 'yus/session'
7
+ require 'needle'
8
+
9
+ VERSION = '1.0.0'
10
+
11
+ module Yus
12
+ class Server
13
+ def initialize(persistence, config, logger)
14
+ @needle = Needle::Registry.new
15
+ @needle.register(:persistence) { persistence }
16
+ @needle.register(:config) { config }
17
+ @needle.register(:logger) { logger }
18
+ @sessions = []
19
+ run_cleaner
20
+ end
21
+ def autosession(domain, &block)
22
+ session = AutoSession.new(@needle, domain)
23
+ block.call(session)
24
+ end
25
+ def login(name, password, domain)
26
+ @needle.logger.info(self.class) {
27
+ sprintf('Login attempt for %s from %s', name, domain)
28
+ }
29
+ hash = @needle.config.digest.hexdigest(password.to_s)
30
+ session = login_root(name, hash, domain) \
31
+ || login_entity(name, hash, domain) # raises YusError
32
+ @sessions.push(session)
33
+ session
34
+ end
35
+ def login_token(name, token, domain)
36
+ entity = authenticate_token(name, token)
37
+ entity.login(domain)
38
+ @needle.persistence.save_entity(entity)
39
+ timeout = entity.get_preference("session_timeout", domain) \
40
+ || @needle.config.session_timeout
41
+ TokenSession.new(@needle, entity, domain)
42
+ end
43
+ def logout(session)
44
+ @needle.logger.info(self.class) {
45
+ sprintf('Logout for %s', session)
46
+ }
47
+ @sessions.delete(session)
48
+ if(session.respond_to?(:destroy!))
49
+ session.destroy!
50
+ end
51
+ end
52
+ def ping
53
+ true
54
+ end
55
+ private
56
+ def authenticate(name, passhash)
57
+ user = @needle.persistence.find_entity(name) \
58
+ or raise UnknownEntityError, "Unknown Entity '#{name}'"
59
+ user.authenticate(passhash) \
60
+ or raise AuthenticationError, "Wrong password"
61
+ @needle.logger.info(self.class) {
62
+ sprintf('Authentication succeeded for %s', name)
63
+ }
64
+ user
65
+ rescue YusError
66
+ @needle.logger.warn(self.class) {
67
+ sprintf('Authentication failed for %s', name)
68
+ }
69
+ raise
70
+ end
71
+ def authenticate_token(name, token)
72
+ user = @needle.persistence.find_entity(name) \
73
+ or raise UnknownEntityError, "Unknown Entity '#{name}'"
74
+ user.authenticate_token(token) \
75
+ or raise AuthenticationError, "Wrong token or token expired"
76
+ @needle.logger.info(self.class) {
77
+ sprintf('Token-Authentication succeeded for %s', name)
78
+ }
79
+ user
80
+ rescue YusError
81
+ @needle.persistence.save_entity(user) if user
82
+ @needle.logger.warn(self.class) {
83
+ sprintf('Token-Authentication failed for %s', name)
84
+ }
85
+ raise
86
+ end
87
+ def clean
88
+ @sessions.delete_if { |session| session.expired? }
89
+ end
90
+ def login_entity(name, passhash, domain)
91
+ entity = authenticate(name, passhash)
92
+ entity.login(domain)
93
+ @needle.persistence.save_entity(entity)
94
+ timeout = entity.get_preference("session_timeout", domain) \
95
+ || @needle.config.session_timeout
96
+ EntitySession.new(@needle, entity, domain)
97
+ end
98
+ def login_root(name, passhash, domain)
99
+ if(name == @needle.config.root_name \
100
+ && passhash == @needle.config.root_pass)
101
+ @needle.logger.info(self.class) {
102
+ sprintf('Authentication succeeded for root: %s', name)
103
+ }
104
+ RootSession.new(@needle)
105
+ end
106
+ end
107
+ def run_cleaner
108
+ @cleaner = Thread.new {
109
+ loop {
110
+ sleep(@needle.config.cleaner_interval)
111
+ clean
112
+ }
113
+ }
114
+ end
115
+ end
116
+ end