yus 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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